JavaScript'te === (katı eşitlik) iki değeri tip dönüşümü yapmadan karşılaştırır; tipler farklıysa sonuç false olur. == (gevşek eşitlik) ise önce tipleri dönüştürüp sonra karşılaştırır, bu yüzden "1" == 1 doğrudur. Object.is() neredeyse === gibi çalışır, ancak NaN değerini kendisine eşit sayar ve +0 ile -0 değerlerini ayırt eder. Günlük kodda varsayılan seçim === olmalıdır.

Bu yazıda üç karşılaştırma yöntemini ve dilin içinde kullanılan dördüncü algoritma olan SameValueZero'yu, NaN ve -0 gibi köşe durumlarla birlikte inceliyoruz.

Dört eşitlik algoritması

Dört eşitlik algoritması: IsLooselyEqual, IsStrictlyEqual, SameValue, SameValueZero

ECMAScript spesifikasyonu dört ayrı eşitlik algoritması tanımlar. Üçüne doğrudan operatör veya metotla ulaşırsınız; dördüncüsü bazı yerleşik metotların içinde çalışır.

AlgoritmaKullanıldığı yerTip dönüşümüNaN ile NaN+0 ile -0
IsLooselyEqual==varfalsetrue
IsStrictlyEqual===, indexOf, lastIndexOf, switch caseyokfalsetrue
SameValueObject.is()yoktruefalse
SameValueZeroincludes, Map ve Set anahtarlarıyoktruetrue

Hiçbiri nesnelerin yapısını karşılaştırmaz. Aynı özelliklere sahip iki ayrı nesne dört algoritmaya göre de eşit değildir.

Katı eşitlik: ===

=== operatöründe hiçbir değer dönüştürülmez. Tipler farklıysa sonuç false, tipler aynıysa değerler karşılaştırılır. Sayılarda iki istisna vardır: NaN hiçbir değere, kendisine bile eşit değildir; +0 ile -0 ise eşit kabul edilir. Nesnelerde yalnızca aynı nesneye başvuruluyorsa sonuç true olur.

javascript
console.log(1 === 1);            // true
console.log(1 === "1");          // false
console.log(null === undefined); // false
console.log(NaN === NaN);        // false
console.log(+0 === -0);          // true

const o = {};
console.log(o === o);            // true
console.log({} === {});          // false

Gevşek eşitlik: == nasıl çalışır?

== simetriktir; a == b ile b == a her zaman aynı sonucu verir. Spesifikasyondaki adımları sadeleştirirsek işleyiş şöyledir:

  • Tipler aynıysa === ile aynı karşılaştırma yapılır.
  • Taraflardan biri null veya undefined ise diğeri de null ya da undefined olmadıkça sonuç false olur.
  • Bir taraf nesne, diğeri ilkel değerse nesne ilkel değere dönüştürülür.
  • Boolean taraf sayıya çevrilir: true 1, false 0 olur.
  • Sayı ile metin karşılaştırılırken metin sayıya çevrilir; çeviri başarısızsa NaN çıkar ve sonuç false olur.
  • Symbol ile Symbol olmayan bir değer hiçbir zaman eşit değildir; BigInt ile Number matematiksel değerlerine göre karşılaştırılır.
javascript
console.log("1" == 1);           // true
console.log(0 == "");            // true
console.log(0 == false);         // true
console.log("" == false);        // true
console.log([1, 2] == "1,2");    // true
console.log(0n == 0);            // true
console.log(null == undefined);  // true
console.log(null == 0);          // false
console.log(null == false);      // false
console.log(undefined == false); // false

Son üç satır şaşırtıcı gelebilir: null ve undefined koşullarda yanlış (falsy) sayılır, ama == ile yalnızca birbirlerine eşittirler. Koşullarda doğru veya yanlış sayılma ile == false sonucu aynı şey değildir; bu konunun ayrıntıları tip dönüşümü yazısında.

NaN: kendisine eşit olmayan değer

NaN: kendisine eşit olmayan değer

NaN, sonucu sayı olarak ifade edilemeyen işlemlerden çıkar (örneğin 0 / 0 veya Number("abc")). === ve == açısından NaN hiçbir şeye eşit değildir; x !== x ifadesinin doğru olduğu tek durum x değerinin NaN olmasıdır. Kontrol için Number.isNaN() kullanın.

Bu durum dizilerde arama yaparken farkı ortaya çıkarır: indexOf() katı eşitlik kullandığı için NaN bulamaz, includes() ise SameValueZero kullandığı için bulur. switch ifadesindeki case NaN dalı da hiçbir zaman eşleşmez.

javascript
const x = Number("abc");
console.log(x);                  // NaN
console.log(x === x);            // false
console.log(Number.isNaN(x));    // true

const dizi = [1, NaN, 3];
console.log(dizi.indexOf(NaN));  // -1
console.log(dizi.includes(NaN)); // true

switch (NaN) {
  case NaN:
    console.log("eşleşti"); // hiçbir şey yazılmaz
}

+0 ve -0

Number tipinde sıfırın iki gösterimi vardır. Günlük işlemlerde fark edilmezler ve === ile eşittirler. Farkı en kolay sıfıra bölmede görürsünüz. Negatif sıfır, sıfır olan bir değerin işaretini çevirdiğinizde veya Math.round(), Math.min() gibi bazı metotların sonucunda ortaya çıkabilir.

javascript
console.log(42 / +0);               // Infinity
console.log(42 / -0);               // -Infinity
console.log(-0 === 0);              // true
console.log(Object.is(-0, 0));      // false
console.log(Object.is(Math.min(-0, +0), -0)); // true

const hiz = 0;
const ters = -hiz;
console.log(Object.is(ters, -0));   // true

Object.is(): SameValue

Object.is() tip dönüşümü yapmaz ve sayılardaki iki istisnayı kaldırır: NaN kendisine eşittir, +0 ile -0 farklıdır. Dil, bir değerin "aynı kimliğe" sahip olup olmadığını kontrol etmesi gereken pek çok yerde bu algoritmayı kullanır. Örneğin değiştirilemez bir özelliği Object.defineProperty() ile yeniden tanımlarken yeni değer eskisiyle SameValue algoritmasına göre karşılaştırılır.

javascript
console.log(Object.is(NaN, NaN));   // true
console.log(Object.is(0, -0));       // false
console.log(Object.is("a", "a"));    // true
console.log(Object.is({}, {}));      // false

MDN'in önerisi açıktır: işaretli sıfırları ayırt etmeniz gerekmiyorsa Object.is() yerine === kullanın. Yalnızca NaN karşılaştırması gerekiyorsa Number.isNaN() ile özel kontrol yapmak çoğu zaman daha anlaşılırdır.

SameValueZero: includes, Map ve Set

SameValueZero, SameValue ile aynıdır; tek farkı +0 ve -0 değerlerini eşit saymasıdır. Doğrudan bir JavaScript API'si olarak sunulmaz, ama Array.prototype.includes(), TypedArray'lerin includes() metodu ve Map ile Set anahtar karşılaştırmaları bu algoritmayı kullanır. Bu yüzden NaN bir Map anahtarı olarak sorunsuz çalışır.

javascript
const m = new Map();
m.set(NaN, "bulundu");
console.log(m.get(NaN)); // bulundu

const s = new Set([0, -0, NaN, NaN]);
console.log(s.size);     // 2

function sameValueZero(x, y) {
  if (typeof x === "number" && typeof y === "number") {
    return x === y || (x !== x && y !== y);
  }
  return x === y;
}
console.log(sameValueZero(NaN, NaN), sameValueZero(0, -0)); // true true

Map ve Set'in diğer özellikleri için Map, Set ve WeakMap yazısına bakabilirsiniz.

Karşılaştırma tablosu

Karşılaştırma tablosu: -0, NaN, false, 0
xy=====Object.isSameValueZero
+0-0truetruefalsetrue
NaNNaNfalsefalsetruetrue
0falsetruefalsefalsefalse
""0truefalsefalsefalse
"17"17truefalsefalsefalse
[1, 2]"1,2"truefalsefalsefalse
nullundefinedtruefalsefalsefalse
nullfalsefalsefalsefalsefalse
{ a: 1 }{ a: 1 }falsefalsefalsefalse

Nesneleri içerik bakımından karşılaştırmak

JavaScript genel amaçlı bir derin eşitlik (deep equality) operatörü sunmaz. İki nesnenin aynı özelliklere sahip olup olmadığını öğrenmek istiyorsanız özellikleri kendiniz karşılaştıran bir fonksiyon yazmanız ya da bir kütüphanenin veya çalışma ortamının sunduğu yardımcıyı kullanmanız gerekir. Basit, düz nesneler için aşağıdaki gibi bir fonksiyon iş görür; iç içe nesneler, diziler ve özel tipler için daha kapsamlı bir çözüm gerekir.

javascript
function duzEsit(a, b) {
  const ka = Object.keys(a);
  const kb = Object.keys(b);
  if (ka.length !== kb.length) return false;
  return ka.every((k) => Object.hasOwn(b, k) && Object.is(a[k], b[k]));
}
console.log(duzEsit({ x: 1, y: 2 }, { y: 2, x: 1 })); // true
console.log(duzEsit({ x: 1 }, { x: "1" }));           // false

Hangi karşılaştırmayı ne zaman kullanmalı?

  • ===: varsayılan seçim. Tip dönüşümü yapmadığı için sonucu tahmin etmek kolaydır.
  • ==: yalnızca ekibinizin bilinçli olarak benimsediği deger == null gibi dar kalıplarda.
  • Object.is(): -0 ile +0 ayrımının gerçekten önemli olduğu durumlarda; örneğin özellik tanımlayıcılarıyla çalışan meta programlama kodunda.
  • Number.isNaN() ve includes(): NaN aramanız veya kontrol etmeniz gerektiğinde.

Bu ayrımı yapmanın pratik bir faydası da vardır: kod incelemesinde == veya Object.is() gördüğünüzde, yazarın bilinçli bir tercih yaptığını ve özel bir durumu ele aldığını anlarsınız.

Sık yapılan hatalar

  • Kullanıcı girdisini == ile sayıyla karşılaştırmak: "" == 0 doğru olduğu için boş girdi sıfır gibi görünür. Girdiyi önce açıkça Number() ile çevirip === kullanın.
  • deger === NaN yazmak: her zaman false verir. Number.isNaN(deger) kullanın.
  • indexOf(NaN) ile arama: sonuç her zaman -1'dir; includes() veya findIndex(Number.isNaN) kullanın.
  • Global isNaN() ile Number.isNaN() farkını gözden kaçırmak: global olan önce argümanı sayıya çevirir, bu yüzden isNaN("abc") doğru döner. Number.isNaN() dönüşüm yapmaz.
  • Nesneleri === ile içerik bakımından karşılaştırmak: yalnızca kimlik karşılaştırılır.

Sık Sorulan Sorular

== mi === mi kullanmalıyım?

Neredeyse her durumda ===. Sonucu tahmin etmek kolaydır ve tip dönüşümü olmadığı için gizli hatalara yol açmaz. == yalnızca deger == null gibi ekibin bilinçli olarak benimsediği kalıplarda tercih edilebilir.

Object.is() ile === arasındaki fark nedir?

Yalnızca iki noktada ayrılırlar: Object.is(NaN, NaN) doğru, NaN === NaN yanlıştır; Object.is(0, -0) yanlış, 0 === -0 doğrudur. Diğer tüm değerlerde aynı sonucu verirler.

Neden [] == false doğru çıkıyor?

Boş dizi önce ilkel değere çevrilir ve "" olur. Boolean taraf sayıya, yani 0'a çevrilir; ardından "" da sayıya çevrilip 0 olur. Sonuç 0 == 0, yani true. Buna rağmen boş dizi bir koşulda doğru (truthy) sayılır.

İki diziyi nasıl karşılaştırırım?

=== yalnızca aynı dizi mi diye bakar. Eleman eleman karşılaştırmak için uzunlukları kontrol edip every() ile her indeksi karşılaştırabilirsiniz. Dizi metotları için map, filter, reduce yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: JavaScript öğrenme rehberi · Tip dönüşümü · JavaScript veri tipleri · Number ve BigInt · Map, Set ve WeakMap