it-swarm.com.de

Ist die Verwendung von == in JavaScript jemals sinnvoll?

In JavaScript, die guten Teile schrieb Douglas Crockford:

JavaScript verfügt über zwei Sätze von Gleichheitsoperatoren: === und !== und ihre bösen Zwillinge == und !=. Die Guten arbeiten so, wie Sie es erwarten würden. Wenn die beiden Operanden vom gleichen Typ sind und den gleichen Wert haben, dann === erzeugt true und !== erzeugt false. Die bösen Zwillinge tun das Richtige, wenn die Operanden vom gleichen Typ sind, aber wenn sie vom unterschiedlichen Typ sind, versuchen sie, die Werte zu erzwingen. Die Regeln, nach denen sie das tun, sind kompliziert und unvergesslich. Dies sind einige der interessanten Fälle:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

Der Mangel an Transitivität ist alarmierend. Mein Rat ist, niemals die bösen Zwillinge zu benutzen. Verwenden Sie stattdessen immer === und !==. Alle soeben gezeigten Vergleiche ergeben false mit dem === Operator.

Gibt es angesichts dieser eindeutigen Beobachtung jemals eine Zeit, in der == könnte eigentlich angebracht sein?

280
Robert Harvey

Ich werde ein Argument für ==

Douglas Crockford, den Sie zitiert haben, ist bekannt für seine vielen und oft sehr nützlichen Meinungen. Während ich in diesem speziellen Fall bei Crockford bin, ist es erwähnenswert, dass es nicht die nur Meinung ist. Es gibt andere wie den Sprachschöpfer Brendan Eich, die das große Problem mit == Nicht sehen. Das Argument sieht ungefähr so ​​aus:

JavaScript ist eine verhaltensorientierte Sprache. Dinge werden basierend auf dem behandelt, was sie tun können und nicht auf ihrem tatsächlichen Typ. Aus diesem Grund können Sie die Methode .map Eines Arrays in einer NodeList oder in einem jQuery-Auswahlsatz aufrufen. Es ist auch der Grund, warum Sie 3 - "5" Tun und etwas Sinnvolles zurückbekommen können - denn "5" kann sich wie eine Zahl verhalten.

Wenn Sie eine == - Gleichheit ausführen, vergleichen Sie den Inhalt einer Variablen und nicht ihren Typ. Hier sind einige Fälle, in denen dies nützlich ist:

  • Lesen einer Nummer vom Benutzer - Lesen Sie den .value Eines Eingabeelements im DOM? Kein Problem! Sie müssen nicht damit beginnen, es zu wirken oder sich Gedanken über seinen Typ zu machen - Sie können es == Sofort auf Zahlen setzen und etwas Sinnvolles zurückbekommen.
  • Müssen Sie nach der "Existenz" einer deklarierten Variablen suchen? - Sie können == null, Da verhaltensmäßig null darstellt, dass dort nichts ist und undefiniert nichts hat dort auch.
  • Müssen Sie überprüfen, ob Sie eine aussagekräftige Eingabe von einem Benutzer erhalten haben? - Überprüfen Sie, ob die Eingabe mit dem Argument == Falsch ist. Es werden Fälle behandelt, in denen der Benutzer nichts oder nur Leerzeichen eingegeben hat für dich ist das wahrscheinlich was du brauchst.

Schauen wir uns Crockfords Beispiele an und erklären sie verhaltensmäßig:

'' == '0'           // got input from user vs. didn't get input - so false
0 == ''             // number representing empty and string representing empty - so true
0 == '0'            // these both behave as the number 0 when added to numbers - so true    
false == 'false'    // false vs got input from user which is truthy - so false
false == '0'        // both can substitute for 0 as numbers - so again true

false == undefined  // having nothing is not the same as having a false value - so false
false == null       // having empty is not the same as having a false value - so false
null == undefined   // both don't represent a value - so true

' \t\r\n ' == 0     // didn't get meaningful input from user vs falsey number - true 

Grundsätzlich ist == So konzipiert, dass es darauf basiert, wie sich Grundelemente verhalten In JavaScript nicht auf dem basieren, was sie sind. Obwohl ich diesem Standpunkt persönlich nicht zustimme, ist es definitiv sinnvoll, dies zu tun - insbesondere, wenn Sie dieses Paradigma der Behandlung von Typen auf der Grundlage des Verhaltens in der gesamten Sprache anwenden.

* Einige bevorzugen vielleicht die häufig vorkommende strukturelle Typisierung des Namens, aber es gibt einen Unterschied - sie sind nicht wirklich daran interessiert, den Unterschied hier zu diskutieren.

233

Es stellt sich heraus, dass jQuery das Konstrukt verwendet

if (someObj == null) {
  // do something
}

ausführlich, als Abkürzung für den entsprechenden Code:

if ((someObj === undefined) || (someObj === null))  {
  // do something
}

Dies ist eine Folge von der ECMAScript-Sprachspezifikation § 11.9.3, Der abstrakte Gleichheitsvergleichsalgorithmus , in der unter anderem Folgendes angegeben ist

1.  If Type(x) is the same as Type(y), then  
    a.  If Type(x) is Undefined, return true.  
    b.  If Type(x) is Null, return true.

und

2.  If x is null and y is undefined, return true.
3.  If x is undefined and y is null, return true.

Diese spezielle Technik ist häufig genug, dass JSHint ein Flag hat, das speziell dafür entwickelt wurde.

95
Robert Harvey

Das Überprüfen der Werte für null oder undefined ist eine Sache, wie ausführlich erläutert wurde.

Es gibt noch eine andere Sache, bei der == Leuchtet:

Sie können den Vergleich von >= So definieren (die Leute beginnen normalerweise mit >, Aber ich finde das eleganter):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < b Und a <= b Werden dem Leser als Übung überlassen.

Wie wir wissen, in JavaScript "3" >= 3 Und "3" <= 3, Von denen Sie 3 == "3" Erhalten. Sie können darauf hinweisen, dass es eine schreckliche Idee ist, einen Vergleich zwischen Zeichenfolgen und Zahlen zu implementieren, indem Sie die Zeichenfolge analysieren. Aber da dies so funktioniert, ist == Absolut der richtige Weg, um diesen Beziehungsoperator zu implementieren.

Das wirklich Gute an == Ist, dass es mit allen anderen Beziehungen übereinstimmt. Anders ausgedrückt, wenn Sie Folgendes schreiben:

function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  return 0;
}

Sie verwenden implizit bereits ==.

Nun zur ganz verwandten Frage: War es eine schlechte Wahl, den Zahlen- und Zeichenfolgenvergleich so zu implementieren, wie er implementiert ist? Für sich betrachtet scheint es eine ziemlich dumme Sache zu sein. Im Zusammenhang mit anderen Teilen von JavaScript und dem DOM ist dies jedoch relativ pragmatisch, wenn man bedenkt, dass:

  • attribute sind immer Zeichenfolgen
  • schlüssel sind immer Zeichenfolgen (der Anwendungsfall besteht darin, dass Sie ein Object verwenden, um eine spärliche Zuordnung von Ints zu Werten zu erhalten).
  • benutzereingaben und Formularsteuerungswerte sind immer Zeichenfolgen (auch wenn die Quelle mit input[type=number] übereinstimmt).

Aus einer ganzen Reihe von Gründen war es sinnvoll, Zeichenfolgen bei Bedarf wie Zahlen zu verhalten. Unter der Annahme, dass der Zeichenfolgenvergleich und die Zeichenfolgenverkettung unterschiedliche Operatoren hatten (z. B. :: Zum Verketten und eine Vergleichsmethode (bei der Sie alle Arten von Parametern in Bezug auf die Groß- und Kleinschreibung verwenden können und was nicht)), wäre dies tatsächlich weniger von einem Durcheinander. Aber diese Operatorüberladung ist wahrscheinlich tatsächlich der Ursprung von "Java" in "JavaScript";)

15
back2dos

Als professioneller Mathematiker sehe ich in Javscript's Gleichheitsoperator== (Auch genannt "abstrakter Vergleich", "lose Gleichheit" ) einen Versuch, ein - zu erstellen. Äquivalenzbeziehung zwischen Entitäten, einschließlich reflexiv , symmetrisch und transitiv . Leider versagen zwei dieser drei grundlegenden Eigenschaften:

== Ist nicht reflexiv :

A == A Kann falsch sein, z.

NaN == NaN // false

== Ist nicht transitiv :

A == B Und B == C Zusammen bedeuten nicht A == C, Z.

'1' == 1 // true
1 == '01' // true
'1' == '01' // false

Nur symmetrisch Eigenschaft überlebt:

A == B Impliziert B == A, Welche Verletzung wahrscheinlich auf jeden Fall undenkbar ist und zu ernsthaften Rebellionen führen würde;)

Warum sind Äquivalenzbeziehungen wichtig?

Denn dies ist die wichtigste und am weitesten verbreitete Art von Beziehung, die durch zahlreiche Beispiele und Anwendungen unterstützt wird. Die wichtigste Anwendung ist die Zerlegung von Entitäten in Äquivalenzklassen , was selbst eine sehr bequeme und intuitive Art ist, Beziehungen zu verstehen. Und das Versagen, Äquivalenz zu sein, führt zum Mangel an Äquivalenzklassen, was wiederum zu dem Mangel an Intuitivität und unnötiger Komplexität führt, die bekannt sind.

Warum ist es so eine schreckliche Idee, == Für eine Nichtäquivalenzbeziehung zu schreiben?

Weil es unsere Vertrautheit und Intuition bricht, ist buchstäblich jedes interessante Verhältnis von Ähnlichkeit, Gleichheit, Kongruenz, Isomorphismus, Identität usw. eine Äquivalenz.

Typkonvertierung

Anstatt sich auf eine intuitive Äquivalenz zu verlassen, führt JavaScript die Typkonvertierung ein:

Der Gleichheitsoperator konvertiert die Operanden, wenn sie nicht vom gleichen Typ sind, und wendet dann einen strengen Vergleich an.

Aber wie ist die Typkonvertierung definiert? Über eine Reihe komplizierter Regeln mit zahlreichen Ausnahmen?

Versuch, eine Äquivalenzbeziehung aufzubauen

Boolesche Werte. Offensichtlich sind true und false nicht gleich und sollten in verschiedenen Klassen sein.

Zahlen. Glücklicherweise ist die Gleichheit der Zahlen bereits genau definiert, wobei zwei verschiedene Zahlen niemals in derselben Äquivalenzklasse liegen. In der Mathematik also. In JavaScript ist der Begriff der Zahl etwas deformiert durch das Vorhandensein der exotischeren -0, Infinity und -Infinity. Unsere mathematische Intuition schreibt vor, dass 0 Und -0 In derselben Klasse sein sollten (tatsächlich ist -0 === 0true), während jede der Unendlichkeiten eine separate Klasse ist .

Zahlen und Boolesche Werte. Wo setzen wir angesichts der Zahlenklassen Boolesche Werte ein? false ähnelt 0, während true1 ähnelt, aber keine andere Zahl:

true == 1 // true
true == 2 // false

Gibt es hier eine Logik, um true mit 1 Zusammenzusetzen? Zugegeben, 1 Wird unterschieden, aber auch -1. Ich persönlich sehe keinen Grund, true in 1 Zu konvertieren.

Und es wird noch schlimmer:

true + 2 // 3
true - 1 // 0

Also wird true tatsächlich unter allen Zahlen in 1 Konvertiert! Ist es logisch? Ist es intuitiv? Die Antwort bleibt als Übung;)

Aber was ist damit:

1 && true // true
2 && true // true

Der einzige boolesche x mit x && true Als true ist x = true. Dies beweist, dass sowohl 1 Als auch 2 (Und jede andere Zahl als 0) In true konvertiert werden! Was es zeigt ist, dass unsere Konvertierung eine andere wichtige Eigenschaft nicht erfüllt - nämlich Bijektion . Dies bedeutet, dass zwei verschiedene Entitäten in dieselbe konvertiert werden können. Was an sich kein großes Problem sein muss. Das große Problem entsteht, wenn wir diese Konvertierung verwenden, um eine Beziehung von "Gleichheit" oder "loser Gleichheit" von dem zu beschreiben, was wir es nennen wollen. Eines ist jedoch klar: Es wird keine Äquivalenzbeziehung sein und es wird nicht intuitiv über Äquivalenzklassen beschrieben.

Aber können wir es besser machen?

Zumindest mathematisch - definitiv ja! Eine einfache Äquivalenzbeziehung zwischen Booleschen Werten und Zahlen könnte so konstruiert werden, dass nur false und 0 In derselben Klasse liegen. false == 0 Wäre also die einzige nicht triviale lose Gleichheit.

Was ist mit Saiten?

Wir können Zeichenfolgen von Leerzeichen am Anfang und am Ende abschneiden, um sie in Zahlen umzuwandeln. Außerdem können wir Nullen vor uns ignorieren:

'   000 ' == 0 // true
'   0010 ' == 10 // true

Wir erhalten also eine einfache Regel für eine Zeichenfolge - schneiden Sie die Leerzeichen und Nullen vor. Entweder erhalten wir eine Zahl oder eine leere Zeichenfolge. In diesem Fall konvertieren wir in diese Zahl oder Null. Oder wir bekommen keine Zahl. In diesem Fall konvertieren wir nicht und erhalten daher keine neue Beziehung.

Auf diese Weise könnten wir tatsächlich eine perfekte Äquivalenzbeziehung für die Gesamtmenge der Booleschen Werte, Zahlen und Zeichenfolgen erhalten! Nur dass ... JavaScript-Designer haben offensichtlich eine andere Meinung:

' ' == '' // false

Die beiden Zeichenfolgen, die beide in 0 Konvertieren, sind sich plötzlich nicht mehr ähnlich! Warum oder warum? Nach der Regel sind Strings genau dann lose gleich, wenn sie genau gleich sind! Wie wir sehen, bricht nicht nur diese Regel die Transitivität, sondern sie ist auch redundant! Was bringt es, einen anderen Operator == Zu erstellen, um ihn streng mit dem anderen === Identisch zu machen?

Fazit

Der lose Gleichheitsoperator == Könnte sehr nützlich gewesen sein, wenn er einige grundlegende mathematische Gesetze eingehalten hätte. Aber wie es leider nicht tut, leidet seine Nützlichkeit.

8
Dmitri Zaitsev

Ja, ich bin auf einen Anwendungsfall gestoßen, nämlich wenn Sie einen Schlüssel mit einem numerischen Wert vergleichen:

for (var key in obj) {
    var some_number = foo(key, obj[key]);  // or whatever -- this is just an example
    if (key == some_number) {
        blah();
    }
}

Ich denke, es ist viel natürlicher, den Vergleich als key == some_number Durchzuführen, anstatt als Number(key) === some_number oder als key === String(some_number).

7
user541686

Ich bin heute auf eine ziemlich nützliche Anwendung gestoßen. Wenn Sie gepolsterte Zahlen vergleichen möchten, z. B. 01 zu normalen ganzen Zahlen, == funktioniert gut. Zum Beispiel:

'01' == 1 // true
'02' == 1 // false

Es erspart Ihnen das Entfernen der 0 und das Konvertieren in eine Ganzzahl.

3
Jon Snow

Ich weiß, dass dies eine späte Antwort ist, aber es scheint eine mögliche Verwirrung über null und undefined zu geben, was meiner Meinung nach == Böse macht, mehr als das Fehlen von Transitivität, die schlimm genug ist. Erwägen:

p1.supervisor = 'Alice';
p2.supervisor = 'None';
p3.supervisor = null;
p4.supervisor = undefined;

Was bedeuten diese?

  • p1 Hat einen Supervisor mit dem Namen "Alice".
  • p2 Hat einen Supervisor mit dem Namen "None".
  • p3 Hat explizit und eindeutig keinen Supervisor .
  • p4 Kann oder kann einen Supervisor haben. Wir wissen es nicht, es ist uns egal, wir sollten es nicht wissen (Datenschutzproblem?), Da es uns nichts angeht.

Wenn Sie == Verwenden, verschmelzen Sie null und undefined, was völlig unpassend ist. Die beiden Begriffe bedeuten völlig unterschiedliche Dinge! Zu sagen, dass ich keinen Vorgesetzten habe, nur weil ich mich geweigert habe, Ihnen zu sagen, wer mein Vorgesetzter ist, ist falsch!

Ich verstehe, dass es Programmierer gibt, denen dieser Unterschied zwischen null und undefined egal ist oder die diese Begriffe anders verwenden. Und wenn Ihre Welt null und undefined nicht richtig verwendet oder Sie diese Begriffe selbst interpretieren möchten, dann sei es so. Ich denke nicht, dass das eine gute Idee ist.

Jetzt habe ich übrigens kein Problem damit, dass null und undefined beide falsch sind! Es ist vollkommen in Ordnung zu sagen

if (p.supervisor) { ... }

und dann würden null und undefined dazu führen, dass der Code, der den Supervisor verarbeitet, übersprungen wird. Das ist richtig, weil wir keinen Vorgesetzten kennen oder haben. Alles gut. Aber die beiden Situationen sind nicht gleich. Deshalb ist == Falsch. Auch hier können die Dinge falsch sein und im Sinne einer Ententypisierung verwendet werden, was sich hervorragend für dynamische Sprachen eignet. Es ist richtiges JavaScript, Pythonic, Rubyish usw. Aber auch diese Dinge sind NICHT gleich.

Und lass mich nicht mit Nicht-Transitivität anfangen: "0x16" == 10, 10 == "10", Aber nicht "10" == "0x16". Ja, JavaScript ist ein schwacher Typ. Ja, es ist zwanghaft. Zwang sollte jedoch niemals für Gleichheit gelten.

Crockford hat übrigens starke Meinungen. Aber weißt du was? Er ist hier richtig!

FWIW Ich verstehe, dass es Situationen gibt, in denen == Bequem ist, und ich bin persönlich darauf gestoßen! Als würde man eine Zeichenfolge für Zahlen eingeben und beispielsweise mit 0 vergleichen. Dies ist jedoch ein Hack. Sie haben Bequemlichkeit als Kompromiss für ein ungenaues Modell der Welt.

TL; DR: Falschheit ist ein großartiges Konzept. Es sollte sich nicht auf Gleichheit erstrecken.

3
Ray Toal