Erklärung der Gleichheitsprüfung in JavaScript: ===, ==, relationale Operatoren und Object.is
Erfahren Sie, wie strenge und lockere Gleichheit, relationale Vergleiche sowie Object.is tatsächlich die Gleichartigkeit in JavaScript bestimmen – sowie die Überlauffallen, die jede dieser Methoden verbirgt.
Quellen: Day 4 of Learning JavaScript: The Three Ways JavaScript Asks “Are These the Same Thing?”
Stellen Sie sich ein Formular vor, in dem ein Benutzer sein Alter als den Text "18" eingibt und eine Überprüfung gegen die Zahl 18 stillschweigend erfolgreich ist – nur um später bei Berechnungen mit diesem Wert Fehler aufzutreten. Oder eine Zugriffsprüfung, die erfolgreich ist, obwohl sie es nicht sein sollte, weil zufällig zwei verschiedene Werte in dasselbe umgewandelt werden konnten. Solche Fehler entstehen aus einem Grund: JavaScript bietet mehrere Möglichkeiten, herauszufinden, ob zwei Werte identisch sind, und jede definiert „identisch“ auf unterschiedliche Weise. Nach dem Lesen dieses Textes werden Sie wissen, was ===, ==, die relationalen Operatoren sowie Object.is() tatsächlich bewirken und in welcher Situation welche Methode verwendet werden sollte.
Warum die Sprache mehr als eine Vorstellung von Gleichheit hat
Frühe Versionen von JavaScript liefen in Browsern, in denen fast alle Eingaben als Text aus Formularen kamen. Anstatt bei jedem Auftreten einer Zahl als Zeichenkette einen Fehler auszulösen, war die Sprache so konzipiert, dass sie nachsichtig war: Sie vermutete, was man meinte, und wandelte die Typen automatisch um. Diese Bequemlichkeit führte zu einer der zuverlässigsten Quellen für Fehler in der Sprache, und die strengeren Operatoren dienen größtenteils dazu, diesem Problem aus dem Weg zu gehen.
Strenge Gleichheit (===) als Standard
Zuerst Typprüfung, dann Wertprüfung
Die strenge Gleichheit führt einen zweistufigen Test durch, bei dem es keine versteckten Umwandlungen gibt. Zunächst werden die Typen der beiden Operanden verglichen. Wenn sie unterschiedlich sind, ist das Ergebnis false, ohne dass die Werte überhaupt geprüft werden. Erst wenn die Typen übereinstimmen, werden die Werte miteinander verglichen.
console.log(1 === 1);
// true (both are numbers, both are 1)
console.log(1 === "1");
// false (the types fail immediately: number versus string)
console.log(0 === false);
// false (number versus boolean)
console.log("" === false);
// false (string versus boolean)
console.log(null === null);
// true
console.log(null === undefined);
// false (different types)
Jeder false-Wert in diesem Auszug entsteht bei der Typüberprüfung: Zahl gegen Zeichenkette, Zahl gegen Boolescher Wert, Zeichenkette gegen Booleschen Wert, null gegen undefined.
Zeichenkettenvergleiche sind bis ins kleinste Zeichen genau – Großschreibung spielt eine Rolle, genauso wie unsichtbare Leerzeichen, die in der Praxis sehr häufig zu Fehlern führen, wenn jemand nach dem Eingeben einer E-Mail-Adresse die Leertaste drückt.
console.log("hello" === "Hello");
// false (different capitalization)
console.log("john@email.com" === "john@email.com ");
// false (the second string has a hidden space at the end)
In der Praxis bedeutet das, Benutzereingaben zu normalisieren – typischerweise mit trim() und oft auch mit toLowerCase() für fälleinsensitive Felder – bevor man sie vergleicht.
Zwei bewusste Ausnahmen von IEEE 754
Strikte Gleichheit ist fast überall zuverlässig, mit zwei numerischen Ausnahmen. Keine davon ist ein Fehler; beide entsprechen dem IEEE 754-Standard für Fließkommazahlen, auf dem JavaScript-Zahlen basieren. Die erste Ausnahme ist, dass NaN, der „keine Zahl“-Wert, mit nichts gleich ist – auch nicht mit sich selbst.
const result = 0 / 0;
// result is NaN
console.log(result === NaN);
// false
console.log(NaN === NaN);
// false
Da eine Gleichheitsprüfung dies niemals erkennen kann, sollte stattdessen die spezielle Funktion Number.isNaN() verwendet werden.
console.log(Number.isNaN(NaN));
// true
Verwenden Sie diese Funktion lieber als die ältere globale Funktion isNaN(), die ihr Argument zunächst in eine Zahl umwandelt und daher für Zeichenfolgen wie "abc" den Wert true zurückgibt. Die zweite Ausnahme betrifft Null: JavaScript repräsentiert positive und negative Null getrennt voneinander, was in einigen numerischen Programmen von Bedeutung ist – doch strikte Gleichheit betrachtet beide als gleich.
console.log(+0 === -0);
// true
Objekte werden nach Identität verglichen
Primitiven wie Zahlen und Zeichenketten werden nach ihrem Wert verglichen. Objekte sind anders: Eine Variable, die ein Objekt enthält, besitzt tatsächlich nur einen Verweis darauf, wo sich dieses Objekt im Speicher befindet. Stellen Sie sich zwei Häuser vor, die nach denselben Plänen gebaut und in derselben Farbe gestrichen wurden, aber sich auf unterschiedlichen Straßen befinden. Sie sehen identisch aus, sind aber nicht dasselbe Haus, weil ihre Adressen unterschiedlich sind.
Wenn Sie Objekte mit === vergleichen, prüft JavaScript, ob beide Seiten auf dasselbe Objekt verweisen, und ignoriert den Inhalt. Arrays folgen derselben Regel, da sie ebenfalls Objekte sind.
const userA = { name: "Alex" };
const userB = { name: "Alex" };
console.log(userA === userB);
// false (two different objects sitting in different memory slots)
console.log([] === []);
// false (two different arrays sitting in different memory slots)
Wenn einer Variablen ein Wert aus einer anderen zugewiesen wird, beziehen sich beide auf dasselbe Objekt, und der Vergleich ist erfolgreich.
const original = { name: "Alex" };
const pointer = original;
console.log(original === pointer);
// true (both point to the exact same memory location)
Um zu überprüfen, ob zwei unterschiedliche Objekte dieselben Daten enthalten, vergleichen Sie selbst ihre primitiven Eigenschaften.
console.log(userA.name === userB.name);
// true (now we are comparing two strings, not two object addresses)
Bei verschachtelten Strukturen entsteht eine rekursive Vergleichsoperation, weshalb Teams in der Regel auf einen getesteten Hilfsfunktion für tiefe Gleichheit aus einer Utility-Bibliothek oder ihrem Testframework zurückgreifen, anstatt eine eigene zu schreiben.
Straffe Ungleichheit (!==)
Die Überprüfung, ob Werte unterschiedlich sind, ist genauso häufig wie die Überprüfung, ob sie übereinstimmen. Straffe Ungleichheit ist das genaue Gegenteil von ===: Sie gibt true zurück, wenn entweder die Typen oder die Werte unterschiedlich sind.
console.log(1 !== 2);
// true (they are different numbers)
console.log(1 !== "1");
// true (one is a number, one is a string)
console.log("apple" !== "orange");
// true
Es eignet sich ideal, wenn Code nur dann ausgeführt werden soll, wenn eine Bedingung nicht erfüllt ist – beispielsweise bei Berechtigungsprüfungen oder beim Aussortieren unerwünschter Elemente.
const currentRole = "guest";
if (currentRole !== "admin") {
console.log("Access denied. Admins only.");
}
// "Access denied. Admins only."
Laxe Gleichheit (==) und ihre Umwandlungen
Wie Zwang überraschende Ergebnisse erzeugt
Bevor die strenge Gleichheit zur Norm wurde, verwendete der größte Teil des Codes ==. Wenn die Typen der Operanden unterschiedlich sind, wandelt die lockere Gleichheit einen oder beide Werte in einen gemeinsamen Typ um und vergleicht sie anschließend. Die Ergebnisse entsprechen schnell nicht mehr der Intuition.
console.log(1 == "1");
// true (the string "1" becomes the number 1)
console.log(0 == false);
// true (false becomes 0)
console.log("" == false);
// true (empty string and false both become 0)
console.log(0 == "");
// true (empty string becomes 0)
console.log([] == "");
// true (empty array becomes empty string)
console.log([1] == 1);
// true (array becomes "1", which becomes 1)
Ein berühmtes Beispiel zeigt, wie viele versteckte Schritte involviert sein können.
console.log([] == 0);
// true
Um true zu erzielen, wandelt der Interpreter zunächst das leere Array in einen primitiven Typ um, was die leere Zeichenkette "" ergibt. Da weiterhin ein Typunterschied besteht – schließlich wird eine Zeichenkette mit einer Zahl verglichen – wird die Zeichenkette in eine Zahl umgewandelt, was 0 ergibt. Der endgültige Vergleich ist 0 == 0, was true bedeutet.
Boolesche Werte sind genauso problematisch, denn wenn sie mit einem nicht-booleschen Wert verglichen werden, werden sie vor allem anderen in Zahlen umgewandelt. true wird dabei zu 1.
console.log(true == "true");
// false
Der String "true" wird nicht zu 1; als Zahl umgewandelt ergibt er NaN, und 1 ist nicht gleich NaN. Deshalb ist value == true fast nie das, was man eigentlich möchte.
null und undefined folgen einer besonderen Regel: bei lockerer Gleichheit sind sie nur untereinander gleich und zu nichts anderem, nicht einmal zu 0, false oder dem leeren String.
console.log(null == undefined);
// true
console.log(null == 0);
// false
console.log(null == false);
// false
console.log(null == "");
// false
console.log(undefined == 0);
// false
Die Null-Prüfung, bei der == vertretbar ist
Loose Equality sollte in modernem Code möglichst vermieden werden, es gibt jedoch eine weit verbreitete Ausnahme. Bei der Verarbeitung von Daten aus einer API möchte man oft wissen, ob ein Wert null oder undefined ist – == null beantwortet diese Frage bereits in einem einzigen Vergleich.
function checkStatus(status) {
if (status == null) {
console.log("Status is missing entirely");
} else {
console.log("Status is:", status);
}
}
checkStatus(null);
// "Status is missing entirely"
checkStatus(undefined);
// "Status is missing entirely"
checkStatus(0);
// "Status is: 0"
Dies funktioniert genau aufgrund der oben genannten Regel: null ist nur locker gleich null und undefined; daher durchlaufen wertlose, aber sinnvolle Werte wie 0 oder "" unverändert den Prozess. Es ersetzt die längere Schreibweise status === null || status === undefined. Beachten Sie, dass viele Teams ihre Linter so konfigurieren, dass == vollständig verboten wird – in diesem Fall muss man die explizite strenge Version verwenden. Die eqeqeq-Regel von ESLint bietet beispielsweise eine Option, die genau diesen Vergleich mit null zulässt, während andere Verwendungen verboten werden. In vielen Fällen drücken der Null-Verknüpfungsoperator ?? oder die optionale Kettensuche ?. die Absicht noch direkter aus.
Lockere Ungleichheit (!=)
!= wendet dieselben Umwandlungen wie == an und verneint anschließend das Ergebnis, wodurch alle unerwarteten Effekte übernommen werden.
console.log(1 != "1");
// false (it converts "1" to a number, decides they are the same, so they are not different)
Hier wird "1" in die Zahl 1 umgewandelt, die Werte gelten als gleich, weshalb die Prüfung auf „nicht gleich“ false zurückgibt. Behandeln Sie != mit derselben Vorsicht wie == und verwenden Sie stattdessen !==.
Relationale Vergleiche: <, >, <= und >=
Zahlen und gemischte Typen
Mit zwei Zahlen verhalten sich die relationalen Operatoren genauso wie in der Arithmetik.
console.log(5 > 3);
// true
console.log(10 <= 10);
// true
Wenn ein String auf eine Zahl trifft, wird der String zunächst in eine Zahl umgewandelt. Ein String, der keine Zahl darstellt, wird zu NaN, und jeder relationale Vergleich mit NaN ergibt false.
console.log("10" > 5);
// true
console.log("abc" > 3);
// false ("abc" becomes NaN, and NaN comparisons always return false)
Wo null und undefined unterschiedlich sind
Relationale Operatoren zeigen eine Inkonsistenz mit der lockeren Gleichheit auf. == weigert sich, null in eine Zahl umzuwandeln, während <, >, <= und >= dies tun, indem sie es in 0 umwandeln.
console.log(null == 0);
// false
console.log(null > 0);
// false (0 is not greater than 0)
console.log(null >= 0);
// true (0 is greater than or equal to 0)
Daher ist null weder gleich 0 noch größer als 0, doch es ist gleich oder größer als 0. Das letzte Ergebnis ergibt Sinn, wenn man weiß, dass a >= b als „a < b ist nicht wahr“ ausgewertet wird und 0 < 0 falsch ist.
undefined verhält sich wieder anders. Wenn es in eine Zahl umgewandelt wird, ergibt es NaN, sodass jede relationale Vergleich mit undefined auf false ausgeht.
console.log(undefined > 0);
// false
console.log(undefined < 0);
// false
Zeichenketten werden nach Codeeinheiten verglichen, nicht nach Wörterbuchreihenfolge
Wenn beide Operanden Zeichenketten sind, findet keine numerische Umrechnung statt. Die Zeichenketten werden Zeichen für Zeichen von links nach rechts verglichen, wobei die numerische UTF-16-Codeeinheit jedes Zeichens verwendet wird.
console.log("apple" < "banana");
// true (a comes before b)
console.log("b" > "a");
// true (b comes after a)
Großbuchstaben haben niedrigere Codeeinheiten als Kleinbuchstaben, was erklärt, warum eine naive Sortierung „Zebra“ vor „apple“ platzieren kann.
console.log("Z" < "a");
// true (Z=90, a=97 in Unicode)
Für eine für Menschen verständliche Sortierung sollten localeCompare() oder Intl.Collator verwendet werden, da diese sprachspezifische Regeln statt reiner Codeeinheiten berücksichtigen.
Die gefährlichere Falle betrifft Zeichenketten, die Ziffern enthalten. Als Zeichenketten verglichen ist „10“ kleiner als „9“.
console.log("10" < "9");
// true (the character "1" is smaller than "9")
JavaScript hat keine Ahnung, dass diese Zeichenfolgen Mengen darstellen. Es betrachtet das erste Zeichen jeder Folge, "1" im Vergleich zu "9", stellt fest, dass "1" zuerst kommt, und gibt daraufhin true aus. Derselbe Effekt tritt auf, wenn ein Array von Zahlen mit sort() ohne Vergleichsfunktion sortiert wird, denn die Standardsortierung wandelt die Elemente vor dem Vergleichen in Zeichenfolgen um; übergeben Sie (a, b) => a - b, um numerisch zu sortieren.
Die Lösung besteht darin, numerische Zeichenfolgen vor dem Vergleich explizit in Zahlen umzuwandeln.
console.log(Number("10") < Number("9"));
// false (correct: 10 is not less than 9)
Object.is() für exakte Gleichheit
Ausfüllen der Lücken in ===
Object.is() ist die präziseste Gleichheitsprüfung, die JavaScript bietet. Für gewöhnliche Werte liefert es dieselben Ergebnisse wie die strenge Gleichheitsprüfung.
console.log(Object.is(1, 1));
// true
console.log(Object.is(1, "1"));
// false
Der Unterschied liegt genau in den beiden Fällen nach IEEE 754. Object.is() betrachtet NaN als gleichwertig mit sich selbst:
console.log(Object.is(NaN, NaN));
// true
und es unterscheidet zwischen positivem Null und negativem Null:
console.log(Object.is(0, -0));
// false
Die drei Überprüfungen nebeneinander
1und"1":==gibttruezurück;===sowieObject.is()gebenfalsezurück.0undfalse:==gibttruezurück;===sowieObject.is()gebenfalsezurück.nullundundefined:==gibttruezurück;===sowieObject.is()gebenfalsezurück.
NaN und NaN: == sowie === liefern false; Object.is() liefert true.+0 und -0: == sowie === liefern true; Object.is() liefert false.[] und [] oder {} und {}: alle drei Fälle liefern false, da es sich um separate Objekte handelt.5 und 5: alle drei Fälle liefern true.Man ruft Object.is() selten direkt auf, doch es spielt eine wichtige Rolle in den Werkzeugen, die man täglich verwendet. React verlässt sich auf Object.is(), um zu entscheiden, ob sich der Zustand oder eine Abhängigkeit geändert hat und daher ein Neuladen erforderlich ist – genau deshalb löst das Mutieren eines Objekts vor Ort keine Aktualisierung aus: Der Referenzwert bleibt identisch. Zustandsbibliotheken wenden denselben Ansatz an; für eine ausführlichere Erklärung siehe wie React-Redux Gleichheitsprüfungen nutzt, um über Neuladungen zu entscheiden.
Kernpunkte
In dem ersten Szenario, bei dem eine Zugriffserlaubnis erteilt wurde, obwohl sie verweigert werden sollte, ist nichts Exotischeres nötig als ==, mit dem eine Zeichenkette mit einer Zahl verglichen wird: Eine stille Umwandlung lässt zwei unterschiedliche Werte gleich erscheinen. Die Regeln, die dies verhindern, sind kurz:
- Verwenden Sie standardmäßig überall
===und!==. - Vermeiden Sie
==und!=, außer beim bewussten Kurzwegvalue == null, sofern Ihre Lint-Regeln dies zulassen. - Erkennen Sie
NaNmitNumber.isNaN(), niemals mit einem Gleichheitsoperator. - Vergessen Sie nicht, dass Objektvergleiche die Identität und nicht den Inhalt prüfen.
- Wandeln Sie numerische Strings mit
Number()um, bevor Sie<oder>verwenden, und geben Siesort()einen Vergleichsfunktor für Zahlen an. - wenden Sie
Object.is()an, wennNaNoder ein vorzeichenbehaftetes Nullwert genau behandelt werden müssen.
JavaScripts nachsichtige Konvertierungen werden nicht verschwinden, aber der Einsatz strenger Vergleiche verhindert, dass sie sich im Produktionsverhalten auswirken.