Les tableaux vides semblent égaler vrai et faux en même temps

201

Les tableaux vides sont vrais mais ils sont également égaux à faux.

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??");

Je suppose que cela est dû à la conversion implicite opérée par l'opérateur d'égalité.

Quelqu'un peut-il expliquer ce qui se passe dans les coulisses?

Patonza
la source
1
Voici un fil similaire qui devrait faire la lumière sur le problème: stackoverflow.com/questions/4226101/…
Rion Williams
2
Remarque, cela arr == truen'évalue pas vrai ;-)
Michael Krelin - hacker
5
Wow ... juste au moment où vous pensiez que vous aviez tout cela.
harpo
3
Pour éviter la coercition de type Javascript WTF, utilisez l'opeartor d'égalité stricte ===. Ensuite, si vous voulez tester le vide d'un tableau, utilisezarr === []
DjebbZ
17
Si vous voulez tester le vide d'un tableau, NE PAS utiliser arr === [], car cela retournera TOUJOURS faux, car le côté droit instancie un nouveau tableau, et la variable de gauche ne peut pas faire référence à quelque chose que vous venez de créer. Le test du vide doit être effectué en levant les yeux arr.length === 0.
Kyle Baker du

Réponses:

274

Vous testez différentes choses ici.

if (arr) appelé sur l'objet (Array est une instance d'Object dans JS) vérifiera si l'objet est présent et retourne vrai / faux.

Lorsque vous appelez, if (arr == false)vous comparez les valeurs de cet objet et la falsevaleur primitive . En interne, arr.toString()est appelé, qui renvoie une chaîne vide "".

Cela est dû au fait que toStringles retours Array.join()de tableaux sont appelés et que la chaîne vide est l'une des valeurs falsifiées en JavaScript.

caractère générique
la source
2
Pouvez-vous expliquer pourquoi Boolean([])revient true?
Devy
11
c'est par convention, dans JS, si les objets sont contraints à Boolean, ils sont toujours forcés à TRUE. regardez le tableau "Contexte booléen" sur: javascript.info/tutorial/object-conversion
Niki
2
@Devy tous les objets en JavaScript sont véridiques, donc la conversion de tout objet en booléen est vraie. Voir 2ality.com/2013/08/objects-truthy.html
Thomson
62

Concernant la ligne:

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

Peut-être que cela aidera:

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

Ce qui, je crois, se produit, c'est que le booléen falseest contraint de le 0comparer à un objet (le côté gauche). L'objet est contraint à une chaîne (la chaîne vide). Ensuite, la chaîne vide est également contrainte en un nombre, à savoir zéro. Et donc la comparaison finale est 0== 0, ce qui est true.

Modifier: voir cette section de la spécification pour plus de détails sur la façon dont cela fonctionne.

Voici ce qui se passe, à partir de la règle n ° 1:

1. Si le type (x) est différent du type (y), passez à l'étape 14.

La prochaine règle qui s'applique est # 19:

19. Si Type (y) est booléen, retournez le résultat de la comparaison x == ToNumber (y).

Le résultat de ToNumber(false)est 0, nous avons donc maintenant:

[] == 0

Encore une fois, la règle n ° 1 nous dit de passer à l'étape n ° 14, mais la prochaine étape qui s'applique réellement est la n ° 21:

21. Si Type (x) est Object et Type (y) est String ou Number, retourne le résultat de la comparaison ToPrimitive (x) == y.

Le résultat de ToPrimitive([])est la chaîne vide, nous avons donc maintenant:

"" == 0

Encore une fois, la règle n ° 1 nous dit de passer à l'étape n ° 14, mais la prochaine étape qui s'applique réellement est la n ° 17:

17. Si Type (x) est String et Type (y) Number, renvoyez le résultat de la comparaison ToNumber (x) == y.

Le résultat de ToNumber("")is 0, qui nous laisse avec:

0 == 0

Maintenant, les deux valeurs ont le même type, donc les étapes continuent de # 1 à # 7, qui dit:

7. Si x est la même valeur numérique que y, retournez true.

Donc, nous revenons true.

En bref:

ToNumber(ToPrimitive([])) == ToNumber(false)
Wayne
la source
2
Grande référence! Pour éviter toute confusion, il peut être utile de mentionner que la raison pour laquelle "la prochaine règle qui s'applique est # 19", même si la règle # 1 dit "passez à l'étape 14", est parce que les étapes 14-18 ne correspondent pas aux types de valeurs comparées.
Sean the Bean
2
Belle explication. Il est déroutant pour moi que les tableaux vides soient considérés comme véridiques, 0 est falsey, et pourtant [] == 0c'est vrai. Je comprends comment cela se produit en fonction de votre explication de la spécification, mais cela semble être un comportement de langage étrange d'un point de vue logique.
bigh_29
7

Pour compléter la réponse de Wayne et essayer d'expliquer pourquoi il ToPrimitive([])revient "", il vaut la peine d'envisager deux types de réponses possibles à la question «pourquoi». Le premier type de réponse est: "parce que la spécification indique que c'est ainsi que JavaScript se comportera." Dans la spécification ES5, section 9.1 , qui décrit le résultat de ToPrimitive comme valeur par défaut pour un objet:

La valeur par défaut d'un objet est récupérée en appelant la méthode interne [[DefaultValue]] de l'objet, en passant l'indicateur facultatif PreferredType.

La section 8.12.8 décrit la [[DefaultValue]]méthode. Cette méthode prend un "indice" comme argument, et l'indice peut être soit String soit Number. Pour simplifier la question en supprimant certains détails, si l'indice est String, [[DefaultValue]]renvoie la valeur de toString()s'il existe et renvoie une valeur primitive et renvoie sinon la valeur de valueOf(). Si l'indice est Number, les priorités de toString()et valueOf()sont inversées de sorte que le valueOf()premier est appelé et sa valeur renvoyée s'il s'agit d'une primitive. Ainsi, si [[DefaultValue]]renvoie le résultat de toString()ou valueOf()dépend du PreferredType spécifié pour l'objet et si ces fonctions renvoient ou non des valeurs primitives.

La valueOf()méthode Object par défaut renvoie simplement l'objet lui-même, ce qui signifie qu'à moins qu'une classe ne remplace la méthode par défaut, valueOf()renvoie simplement l'objet lui-même. C'est le cas pour Array. [].valueOf()renvoie l'objet []lui-même. Puisqu'un Arrayobjet n'est pas une primitive, l' [[DefaultValue]]indice n'est pas pertinent: la valeur de retour pour un tableau sera la valeur de toString().

Pour citer le JavaScript de David Flanagan : le guide définitif , qui, soit dit en passant, est un superbe livre qui devrait être le premier endroit de chacun pour obtenir des réponses à ces types de questions:

Les détails de cette conversion d'objet en nombre expliquent pourquoi un tableau vide se convertit au nombre 0 et pourquoi un tableau avec un seul élément peut également se convertir en un nombre. Les tableaux héritent de la méthode valueOf () par défaut qui renvoie un objet plutôt qu'une valeur primitive, de sorte que la conversion de tableau en nombre repose sur la méthode toString (). Les tableaux vides sont convertis en chaîne vide. Et la chaîne vide est convertie en nombre 0. Un tableau avec un seul élément est converti en la même chaîne que cet élément. Si un tableau contient un seul nombre, ce nombre est converti en chaîne, puis de nouveau en nombre.

Le deuxième type de réponse à la question «pourquoi», autre que «parce que la spécification dit», explique pourquoi le comportement est logique du point de vue de la conception. Sur cette question, je ne peux que spéculer. Premièrement, comment convertir un tableau en nombre? La seule possibilité raisonnable à laquelle je peux penser serait de convertir un tableau vide en 0 et tout tableau non vide en 1. Mais comme l'a révélé la réponse de Wayne, un tableau vide sera converti en 0 pour de nombreux types de comparaisons de toute façon. Au-delà de cela, il est difficile de penser à une valeur de retour primitive sensible pour Array.valueOf (). On pourrait donc dire qu'il est plus logique d'avoir Array.valueOf()la valeur par défaut et de renvoyer le tableau lui-même, ce toString()qui conduit au résultat utilisé par ToPrimitive. Il est plus logique de convertir un tableau en chaîne plutôt qu'en nombre.

De plus, comme l'indique la citation de Flanagan, cette décision de conception permet certains types de comportements bénéfiques. Par exemple:

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

Ce comportement vous permet de comparer un tableau à un élément aux nombres et d'obtenir le résultat attendu.

cjg
la source
Merci pour cette réponse, c'est une explication assez détaillée que la question manquait.
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
la source
2

Dans if (arr), il est toujours évalué (ToBoolean) à true si arr est un objet car tous les objets en JavaScript sont véridiques . (null n'est pas un objet!)

[] == falseest évalué en approche itérative. Au début, si un côté de ==est primitif et l'autre est objet, il convertit d'abord l'objet en primitif, puis convertit les deux côtés en Nombre si les deux côtés ne le sont pas string(la comparaison de chaînes est utilisée si les deux côtés sont des chaînes). La comparaison est donc répétée comme, [] == false-> '' == false-> 0 == 0-> true.

Thomson
la source
2

Exemple:

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

Cela arrive parce que

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []est un Arrayobjet vide → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → vrai
yqbk
la source
1

Un tableau avec des éléments (que ce soit 0, faux ou un autre tableau vide), se résout toujours à trueutiliser la comparaison d'égalité abstraite ==.

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

Mais en utilisant une comparaison stricte d'égalité ===, vous essayez d'évaluer le contenu de la variable ainsi que son type de données, c'est pourquoi:

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
la source
-1

Vous pouvez vider un tableau JavaScript en le référençant à un nouveau tableau, en utilisant list = []ou en supprimant les éléments du tableau actuellement référencé list.length = 0.

Source: tableau vide JavaScript

AliveXhd
la source
-2

Rien de ce qui précède ne m'a aidé, lorsque j'essayais d'utiliser le plugin de cartographie knockout.js, peut-être car un "tableau vide" n'est pas vraiment vide.

J'ai fini par utiliser: data-bind="if: arr().length"qui a fait l'affaire.

Ceci est spécifique au KO, pas à la question du PO, mais peut-être que cela aidera quelqu'un d'autre à naviguer ici dans une situation similaire.

domoarigato
la source
Cette réponse n'est pas liée
fauverisme
seulement tangentiellement, comme
déconseillé