Leere Arrays scheinen gleichzeitig wahr und falsch zu sein

201

Leere Arrays sind wahr, aber sie sind auch gleich falsch.

var arr = [];
console.log('Array:', arr);
if (arr) console.log("It's true!");
if (arr == false) console.log("It's false!");
if (arr && arr == false) console.log("...what??");

Ich denke, das liegt an der impliziten Konvertierung, die vom Gleichheitsoperator ausgeführt wird.

Kann jemand erklären, was hinter den Kulissen los ist?

Patonza
quelle
1
Hier ist ein ähnlicher Thread, der etwas Licht in das Problem bringen sollte: stackoverflow.com/questions/4226101/…
Rion Williams
2
Beachten Sie, dass arr == truedies nicht als wahr bewertet wird ;-)
Michael Krelin - Hacker
5
Wow ... gerade als du dachtest, du hättest das alles im Griff.
Harpo
3
Verwenden Sie den strengen Gleichheitsoperator, um den Zwang vom Typ Javascript WTF zu vermeiden ===. Wenn Sie dann die Leere eines Arrays testen möchten, verwenden Siearr === []
DjebbZ
17
Wenn Sie die Leere eines Arrays testen möchten, verwenden Sie NICHT arr === [], da dies IMMER false zurückgibt, da die rechte Seite ein neues Array instanziiert und die Variable auf der linken Seite nicht auf etwas verweisen kann, das Sie gerade erstellt haben. Das Testen der Leere sollte durch Nachschlagen erfolgen arr.length === 0.
Kyle Baker

Antworten:

274

Sie testen hier verschiedene Dinge.

if (arr) Das aufgerufene Objekt (Array ist eine Instanz von Object in JS) prüft, ob das Objekt vorhanden ist, und gibt true / false zurück.

Wenn Sie aufrufen if (arr == false), vergleichen Sie die Werte dieses Objekts mit dem primitiven falseWert. Intern arr.toString()wird aufgerufen, was eine leere Zeichenfolge zurückgibt "".

Dies liegt daran, dass toStringaufgerufene Array-Rückgaben Array.join()und leere Zeichenfolgen einer der falschen Werte in JavaScript sind.

Platzhalter
quelle
2
Können Sie erklären, warum Boolean([])zurückkommt true?
Devy
11
Das ist in JS üblich. Wenn in JS Objekte zu Boolean gezwungen werden, werden sie immer zu TRUE gezwungen. Schauen Sie sich die Tabelle "Boolescher Kontext" an unter: javascript.info/tutorial/object-conversion
Niki
2
@Devy Alle Objekte in JavaScript sind wahr, daher ist die Konvertierung aller Objekte in Boolean wahr. Siehe 2ality.com/2013/08/objects-truthy.html
Thomson
62

In Bezug auf die Linie:

if (arr == false) console.log("It's false!");

Vielleicht helfen diese:

console.log(0 == false) // true
console.log([] == 0) // true
console.log([] == "") // true

Was meiner Meinung nach passiert, ist, dass der Boolesche falseWert zum 0Vergleich mit einem Objekt (der linken Seite) erzwungen wird . Das Objekt wird zu einer Zeichenfolge (der leeren Zeichenfolge) gezwungen. Dann wird die leere Zeichenfolge ebenfalls zu einer Zahl gezwungen, nämlich Null. Und so ist der endgültige Vergleich 0== 0, was isttrue .

Bearbeiten: Siehe diesen Abschnitt der Spezifikation für Details zu genau , wie das funktioniert.

Folgendes passiert ab Regel 1:

1. Wenn sich Typ (x) von Typ (y) unterscheidet, fahren Sie mit Schritt 14 fort.

Die nächste geltende Regel lautet # 19:

19. Wenn Typ (y) boolesch ist, geben Sie das Ergebnis des Vergleichs x == ToNumber (y) zurück.

Das Ergebnis von ToNumber(false)ist 0, also haben wir jetzt:

[] == 0

Wieder sagt uns Regel 1, dass wir zu Schritt 14 springen sollen, aber der nächste Schritt, der tatsächlich gilt, ist # 21:

21. Wenn Typ (x) Objekt und Typ (y) entweder Zeichenfolge oder Zahl ist, geben Sie das Ergebnis des Vergleichs ToPrimitive (x) == y zurück.

Das Ergebnis von ToPrimitive([])ist die leere Zeichenfolge, also haben wir jetzt:

"" == 0

Wieder sagt uns Regel 1, dass wir zu Schritt 14 springen sollen, aber der nächste Schritt, der tatsächlich gilt, ist # 17:

17. Wenn Type (x) String und Type (y) Number ist, geben Sie das Ergebnis des Vergleichs ToNumber (x) == y zurück.

Das Ergebnis von ToNumber("")ist 0, was uns übrig lässt mit:

0 == 0

Jetzt haben beide Werte den gleichen Typ, sodass die Schritte von Nr. 1 bis Nr. 7 fortgesetzt werden. Darin heißt es:

7. Wenn x der gleiche Zahlenwert wie y ist, geben Sie true zurück.

Also kehren wir zurück true.

In Kürze:

ToNumber(ToPrimitive([])) == ToNumber(false)
Wayne
quelle
2
Tolle Referenz! Um Verwirrung zu vermeiden, kann es hilfreich sein zu erwähnen, dass der Grund "die nächste geltende Regel ist # 19", obwohl Regel # 1 "gehe zu Schritt 14" lautet, darin besteht, dass die Schritte 14-18 nicht mit den Typen der übereinstimmen Werte verglichen werden.
Sean the Bean
2
Schöne Erklärung. Es ist mir ein Rätsel, dass leere Arrays als wahr angesehen werden, 0 ist falsch und doch [] == 0wahr. Ich verstehe, wie dies geschieht, basierend auf Ihrer Erklärung der Spezifikation, aber aus logischer Sicht scheint es ein seltsames Sprachverhalten zu sein.
bigh_29
7

Um Waynes Antwort zu ergänzen und zu erklären, warum sie ToPrimitive([])zurückkehrt "", sollten zwei mögliche Arten von Antworten auf die Warum-Frage in Betracht gezogen werden. Die erste Art der Antwort lautet: "Weil die Spezifikation besagt, dass sich JavaScript so verhält." In der ES5-Spezifikation, Abschnitt 9.1 , der das Ergebnis von ToPrimitive als Standardwert für ein Objekt beschreibt:

Der Standardwert eines Objekts wird durch Aufrufen der internen Methode [[DefaultValue]] des Objekts abgerufen, wobei der optionale Hinweis PreferredType übergeben wird.

Abschnitt 8.12.8 beschreibt die [[DefaultValue]]Methode. Diese Methode verwendet einen "Hinweis" als Argument, und der Hinweis kann entweder String oder Number sein. Um die Angelegenheit zu vereinfachen, indem auf einige Details verzichtet wird, gibt der Hinweis, wenn er String ist, [[DefaultValue]]den Wert von zurück, toString()falls vorhanden, und gibt einen primitiven Wert zurück, andernfalls den Wert von valueOf(). Wenn der Hinweis Number ist, werden die Prioritäten von toString()und valueOf()umgekehrt, sodass valueOf()zuerst aufgerufen und der Wert zurückgegeben wird, wenn es sich um ein Grundelement handelt. Also, ob [[DefaultValue]]das Ergebnis von toString()oder zurückgibtvalueOf() für den angegebenen PreferredType für das Objekt zurückgegeben wird und ob diese Funktionen primitive Werte zurückgeben oder nicht.

Die Standardobjektmethode gibt valueOf()nur das Objekt selbst zurück. Wenn eine Klasse die Standardmethode nicht überschreibt, wird valueOf()nur das Objekt selbst zurückgegeben. Dies ist der Fall für Array. [].valueOf()gibt das Objekt []selbst zurück. Da ein ArrayObjekt kein Grundelement ist, ist der [[DefaultValue]]Hinweis irrelevant: Der Rückgabewert für ein Array ist der Wert vontoString() .

Um David Flanagans JavaScript zu zitieren : The Definitive Guide , das übrigens ein hervorragendes Buch ist, das der erste Ort sein sollte, an dem jeder Antworten auf diese Art von Fragen erhält:

Die Details dieser Konvertierung von Objekt in Nummer erklären, warum ein leeres Array in die Nummer 0 konvertiert wird und warum ein Array mit einem einzelnen Element auch in eine Nummer konvertiert werden kann. Arrays erben die Standardmethode valueOf (), die ein Objekt anstelle eines primitiven Werts zurückgibt. Daher hängt die Konvertierung von Array in Nummer von der Methode toString () ab. Leere Arrays werden in die leere Zeichenfolge konvertiert. Und die leere Zeichenfolge wird in die Zahl 0 konvertiert. Ein Array mit einem einzelnen Element wird in dieselbe Zeichenfolge konvertiert wie dieses eine Element. Wenn ein Array eine einzelne Nummer enthält, wird diese Nummer in eine Zeichenfolge und dann zurück in eine Nummer konvertiert.

Die zweite Art der Antwort auf die "Warum" -Frage, abgesehen von "Weil die Spezifikation sagt", gibt eine Erklärung dafür, warum das Verhalten aus der Designperspektive sinnvoll ist. Zu diesem Thema kann ich nur spekulieren. Wie würde man ein Array in eine Zahl umwandeln? Die einzig sinnvolle Möglichkeit, die ich mir vorstellen kann, wäre, ein leeres Array in 0 und jedes nicht leere Array in 1 zu konvertieren. Wie Waynes Antwort jedoch ergab, wird ein leeres Array für viele Arten von Vergleichen ohnehin in 0 konvertiert. Darüber hinaus fällt es schwer, sich einen sinnvollen primitiven Rückgabewert für Array.valueOf () vorzustellen. Man könnte also argumentieren, dass es nur sinnvoller ist Array.valueOf(), der Standard zu sein und das Array selbst zurückzugebentoString() zu dem von ToPrimitive verwendeten Ergebnis führt. Es ist nur sinnvoller, ein Array in eine Zeichenfolge anstatt in eine Zahl zu konvertieren.

Darüber hinaus ermöglicht diese Entwurfsentscheidung, wie aus dem Flanagan-Zitat hervorgeht, bestimmte Arten von vorteilhaften Verhaltensweisen. Zum Beispiel:

var a = [17], b = 17, c=1;
console.log(a==b);      // <= true
console.log(a==c);      // <= false

Mit diesem Verhalten können Sie ein Einzelelementarray mit Zahlen vergleichen und das erwartete Ergebnis erhalten.

cjg
quelle
Vielen Dank für diese Antwort, das ist eine ziemlich detaillierte Erklärung, dass die Frage gefehlt hat.
Estus Flask
3
console.log('-- types: undefined, boolean, number, string, object --');
console.log(typeof undefined);  // undefined
console.log(typeof null);       // object
console.log(typeof NaN);        // number
console.log(typeof false);      // boolean
console.log(typeof 0);          // number
console.log(typeof "");         // string
console.log(typeof []);         // object
console.log(typeof {});         // object

console.log('-- Different values: NotExist, Falsy, NaN, [], {} --');
console.log('-- 1. NotExist values: undefined, null have same value --');
console.log(undefined == null); // true

console.log('-- 2. Falsy values: false, 0, "" have same value --');
console.log(false == 0);        // true
console.log(false == "");       // true
console.log(0 == "");           // true

console.log('-- 3. !NotExist, !Falsy, and !NaN return true --');
console.log(!undefined);        // true
console.log(!null);             // true

console.log(!false);            // true
console.log(!"");               // true
console.log(!0);                // true

console.log(!NaN);              // true

console.log('-- 4. [] is not falsy, but [] == false because [].toString() returns "" --');
console.log(false == []);       // true
console.log([].toString());     // ""

console.log(![]);               // false

console.log('-- 5. {} is not falsy, and {} != false, because {}.toString() returns "[object Object]" --');
console.log(false == {});       // false
console.log({}.toString());     // [object Object]

console.log(!{});               // false

console.log('-- Comparing --');
console.log('-- 1. string will be converted to number or NaN when comparing with a number, and "" will be converted to 0 --');
console.log(12 < "2");          // false
console.log("12" < "2");        // true
console.log("" < 2);            // true

console.log('-- 2. NaN can not be compared with any value, even if NaN itself, always return false --');
console.log(NaN == NaN);        // false

console.log(NaN == null);       // false
console.log(NaN == undefined);  // false
console.log(0 <= NaN);          // false
console.log(0 >= NaN);          // false
console.log(undefined <= NaN);  // false
console.log(undefined >= NaN);  // false
console.log(null <= NaN);       // false
console.log(null >= NaN);       // false

console.log(2 <= "2a");         // false, since "2a" is converted to NaN
console.log(2 >= "2a");         // false, since "2a" is converted to NaN

console.log('-- 3. undefined can only == null and == undefined, and can not do any other comparing even if <= undefined --');
console.log(undefined == null);         // true
console.log(undefined == undefined);    // true

console.log(undefined == "");           // false
console.log(undefined == false);        // false
console.log(undefined <= undefined);    // false
console.log(undefined <= null);         // false
console.log(undefined >= null);         // false
console.log(0 <= undefined);            // false
console.log(0 >= undefined);            // false

console.log('-- 4. null will be converted to "" when <, >, <=, >= comparing --');
console.log(12 <= null);        // false
console.log(12 >= null);        // true
console.log("12" <= null);      // false
console.log("12" >= null);      // true

console.log(0 == null);         // false
console.log("" == null);        // false

console.log('-- 5. object, including {}, [], will be call toString() when comparing --');
console.log(12 < {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log(12 > {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log("[a" < {});         // true, since {}.toString() is "[object Object]"
console.log("[a" > {});         // false, since {}.toString() is "[object Object]"
console.log(12 < []);           // false, since {}.toString() is "", and then converted to 0
console.log(12 > []);           // true, since {}.toString() is "", and then converted to 0
console.log("[a" < []);         // false, since {}.toString() is ""
console.log("[a" > []);         // true, since {}.toString() is ""

console.log('-- 6. According to 4 and 5, we can get below weird result: --');
console.log(null < []);         // false
console.log(null > []);         // false
console.log(null == []);        // false
console.log(null <= []);        // true
console.log(null >= []);        // true
Tom Jiang
quelle
2

In if (arr) wird es immer als true ausgewertet (ToBoolean), wenn arr ein Objekt ist, da alle Objekte in JavaScript wahr sind . (null ist kein Objekt!)

[] == falsewird im iterativen Ansatz bewertet. Wenn eine Seite von ==primitiv und die andere objekt ist, konvertiert sie zuerst Objekt in primitiv und konvertiert dann beide Seiten in Zahl, wenn beide Seiten nicht sind string(Zeichenfolgenvergleich wird verwendet, wenn beide Seiten Zeichenfolgen sind). Der Vergleich wird also wie folgt wiederholt: [] == false-> '' == false-> 0 == 0-> true.

Thomson
quelle
2

Beispiel:

const array = []
const boolValueOfArray = !!array // true

Es passiert weil

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []ist leeres ArrayObjekt → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → wahr
yqbk
quelle
1

Ein Array mit Elementen (unabhängig davon, ob 0, false oder ein anderes leeres Array) wird immer truemit Abstract Abstract Equality Comparison aufgelöst ==.

1. [] == false; // true, because an empty array has nothing to be truthy about
2. [2] == false; // false because it has at least 1 item
3. [false] == false; // also false because false is still an item
4. [[]] == false; // false, empty array is still an item

Mit einem strengen Gleichheitsvergleich ===versuchen Sie jedoch, den Inhalt der Variablen sowie ihren Datentyp zu bewerten. Deshalb:

1. [] === false; // false, because an array (regardless of empty or not) is not strictly comparable to boolean `false`
2. [] === true; // false, same as above, cannot strictly compare [] to boolean `true`
3. [[]] === false; // true, because see #1
Aldee
quelle
-1

Sie können ein JavaScript-Array leeren, indem Sie es auf ein neues Array verweisen und list = []die Elemente des aktuell referenzierten Arrays verwenden oder löschen list.length = 0.

Quelle: JavaScript Empty Array

AliveXhd
quelle
-2

Keiner der oben genannten Punkte hat mir beim Versuch geholfen, das Mapping-Plugin knockout.js zu verwenden, vielleicht weil ein "leeres Array" nicht wirklich leer ist.

Am Ende habe ich verwendet: data-bind="if: arr().length" was den Trick gemacht hat.

Dies ist spezifisch für das Knockout, nicht für die Frage des OP, aber vielleicht hilft es jemand anderem, hier in einer ähnlichen Situation zu surfen.

domoarigato
quelle
Diese Antwort ist nicht verwandt
Fauverismus
nur tangential, wie
abgelehnt