Matrizes vazias parecem iguais a verdade e falsa ao mesmo tempo

201

Matrizes vazias são verdadeiras, mas também são iguais a falsas.

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

Eu acho que isso se deve à conversão implícita operada pelo operador de igualdade.

Alguém pode explicar o que está acontecendo nos bastidores?

Patonza
fonte
1
Aqui está um tópico semelhante que deve lançar alguma luz sobre o problema: stackoverflow.com/questions/4226101/…
Rion Williams
2
Observe que arr == trueisso não é avaliado como verdadeiro ;-) #
22460 Michael Krelin - hacker 30/03
5
Uau ... exatamente quando você pensou que tinha tudo isso para baixo.
harpo 30/03
3
Para evitar o WTF de coerção do tipo Javascript, use o operador de igualdade estrita ===. Então, se você quiser testar o vazio de uma matriz, usearr === []
DjebbZ 21/12
17
Se você deseja testar o vazio de uma matriz, NÃO use arr === [], pois isso SEMPRE retornará falso, pois o lado direito está instanciando uma nova matriz e a variável à esquerda não pode se referir a algo que você acabou de criar. O vazio do teste deve ser feito olhando para cima arr.length === 0.
Kyle Baker

Respostas:

274

Você está testando coisas diferentes aqui.

if (arr) O objeto chamado (Matriz é a instância do Objeto em JS) verificará se o objeto está presente e retorna verdadeiro / falso.

Quando você chama, if (arr == false)compara os valores desse objeto e o falsevalor primitivo . Internamente, arr.toString()é chamado, que retorna uma string vazia "".

Isso ocorre porque o toStringretorno da matriz é chamado Array.join()e a string vazia é um dos valores falsos no JavaScript.

curinga
fonte
2
Você pode explicar por que Boolean([])retorna true?
Devy #
11
por convenção, em JS, se os objetos são coagidos a Boolean, eles sempre são coagidos a TRUE. olhar para a tabela de "contexto booleano" em: javascript.info/tutorial/object-conversion
Niki
2
@Devy todos os objetos em JavaScript são verdadeiros; portanto, a conversão de qualquer objeto em Boolean é verdadeira. Veja 2ality.com/2013/08/objects-truthy.html
Thomson
62

Em relação à linha:

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

Talvez isso ajude:

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

O que eu acredito que está acontecendo é que o booleano falseé coagido 0para comparação com um objeto (o lado esquerdo). O objeto é coagido a uma sequência (a sequência vazia). Então, a cadeia vazia também é coagida em um número, ou seja, zero. E assim a comparação final é 0== 0, que é true.

Editar: Veja esta seção da especificação para obter detalhes sobre exatamente como isso funciona.

Aqui está o que está acontecendo, começando na regra 1:

1. Se o Tipo (x) for diferente do Tipo (y), vá para a etapa 14.

A próxima regra que se aplica é # 19:

19. Se Type (y) for Boolean, retorne o resultado da comparação x == ToNumber (y).

O resultado de ToNumber(false)é 0, então agora temos:

[] == 0

Mais uma vez, a regra 1 nos diz que devemos pular para a etapa 14, mas a próxima etapa que se aplica realmente é a 21:

21. Se Type (x) for Object e Type (y) for String ou Number, retorne o resultado da comparação ToPrimitive (x) == y.

O resultado de ToPrimitive([])é a string vazia, então agora temos:

"" == 0

Mais uma vez, a regra 1 nos diz que devemos pular para a etapa 14, mas a próxima etapa que realmente se aplica é a 17:

17. Se Type (x) for String e Type (y) for Number, retorne o resultado da comparação ToNumber (x) == y.

O resultado de ToNumber("")é 0, o que nos deixa com:

0 == 0

Agora, os dois valores têm o mesmo tipo, portanto, as etapas continuam do 1 ao 7, que diz:

7. Se x é o mesmo valor numérico que y, retorne true.

Então, voltamos true.

Em resumo:

ToNumber(ToPrimitive([])) == ToNumber(false)
Wayne
fonte
2
Ótima referência! Para evitar confusão, pode ser útil mencionar que a razão "a próxima regra que se aplica é a nº 19", mesmo que a regra nº 1 diga "vá para a etapa 14", é porque as etapas 14 a 18 não correspondem aos tipos de valores sendo comparados.
Sean the Bean
2
Boa explicação. É intrigante para mim que matrizes vazias sejam consideradas [] == 0verdadeiras , 0 é falsey e, no entanto, é verdade. Entendo como isso acontece com base na sua explicação sobre as especificações, mas parece um comportamento estranho da linguagem do ponto de vista lógico.
precisa saber é
7

Para complementar a resposta de Wayne e tentar explicar por que ToPrimitive([])retorna "", vale a pena considerar dois tipos possíveis de respostas para a pergunta 'por que'. O primeiro tipo de resposta é: "porque a especificação diz que é assim que o JavaScript se comportará". Na especificação do ES5, seção 9.1 , que descreve o resultado de ToPrimitive como um valor padrão para um Objeto:

O valor padrão de um objeto é recuperado chamando o método interno [[DefaultValue]] do objeto, passando a dica opcional PreferredType.

Seção 8.12.8 descreve o [[DefaultValue]]método. Esse método usa uma "dica" como argumento e a dica pode ser String ou Number. Para simplificar o assunto dispensando alguns detalhes, se a dica for String, [[DefaultValue]]retornará o valor de toString()se ele existir e retornará um valor primitivo e, caso contrário, retornará o valor de valueOf(). Se a dica for Number, as prioridades de toString()e valueOf()serão revertidas para que valueOf()seja chamado primeiro e seu valor retornado se for um primitivo. Portanto, se [[DefaultValue]]retorna o resultado toString()ou valueOf()depende do PreferredType especificado para o objeto e se essas funções retornam ou não valores primitivos.

O valueOf()método Object padrão apenas retorna o próprio objeto, o que significa que, a menos que uma classe substitua o método padrão, valueOf()apenas retorna o próprio objeto. Este é o caso Array. [].valueOf()retorna o []próprio objeto . Como um Arrayobjeto não é primitivo, a [[DefaultValue]]dica é irrelevante: o valor de retorno para uma matriz será o valor de toString().

Para citar o JavaScript de David Flanagan : The Definitive Guide , que, a propósito, é um livro excelente que deve ser o primeiro lugar de todos para obter respostas para esses tipos de perguntas:

Os detalhes dessa conversão de objeto em número explicam por que uma matriz vazia se converte no número 0 e por que uma matriz com um único elemento também pode ser convertida em um número. As matrizes herdam o método valueOf () padrão que retorna um objeto em vez de um valor primitivo, portanto, a conversão de matriz em número depende do método toString (). Matrizes vazias são convertidas para a sequência vazia. E a sequência vazia é convertida no número 0. Uma matriz com um único elemento é convertida na mesma sequência que esse elemento. Se uma matriz contiver um único número, esse número será convertido em uma sequência e, em seguida, voltará a um número.

O segundo tipo de resposta para a pergunta "por que", exceto "porque a especificação diz", fornece algumas explicações sobre por que o comportamento faz sentido da perspectiva do design. Sobre esta questão, só posso especular. Primeiro, como converter uma matriz em um número? A única possibilidade sensata que consigo pensar seria converter uma matriz vazia em 0 e qualquer matriz não vazia em 1. Mas, como a resposta de Wayne revelou, uma matriz vazia será convertida em 0 para muitos tipos de comparações de qualquer maneira. Além disso, é difícil pensar em um valor de retorno primitivo sensível para Array.valueOf (). Portanto, pode-se argumentar que faz mais sentido ter Array.valueOf()o padrão e retornar a própria matriz, levando toString()ao resultado usado pelo ToPrimitive. Faz mais sentido converter uma matriz em uma cadeia de caracteres do que em um número.

Além disso, como sugerido pela citação de Flanagan, essa decisão de design permite certos tipos de comportamentos benéficos. Por exemplo:

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

Esse comportamento permite comparar uma matriz de elemento único com números e obter o resultado esperado.

cjg
fonte
Obrigado por esta resposta, essa é uma explicação bastante detalhada que faltava à pergunta.
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
fonte
2

Em if (arr), é sempre avaliado (ToBoolean) como true se arr for um objeto, pois todos os objetos em JavaScript são verdadeiros . (null não é um objeto!)

[] == falseé avaliado em abordagem iterativa. Inicialmente, se um lado de ==é primitivo e o outro é objeto, ele converte o objeto em primitivo no início e, em seguida, converte os dois lados em Number se ambos os lados não estiverem string(a comparação de strings é usada se os dois lados forem strings). Portanto, a comparação é iterada como, [] == false-> '' == false-> 0 == 0->true .

Thomson
fonte
2

Exemplo:

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

Isso acontece porque

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []está um Arrayobjeto vazio → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → verdadeiro
yqbk
fonte
1

Uma matriz com elementos (independentemente de 0, falso ou outra matriz vazia), sempre resolve trueusar a Comparação de Igualdade Abstrata ==.

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

Mas, usando uma comparação estrita de igualdade ===, você está tentando avaliar o conteúdo da variável e o tipo de dados, por isso:

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
fonte
-1

Você pode esvaziar uma matriz JavaScript referenciando-a em uma nova matriz, usando list = []ou excluindo os elementos da matriz atualmente referenciadalist.length = 0 .

Origem: JavaScript Empty Array

AliveXhd
fonte
-2

Nenhuma das opções acima me ajudou, ao tentar usar o plug-in de mapeamento knockout.js, talvez porque um "array vazio" não esteja realmente vazio.

Acabei usando: data-bind="if: arr().length"qual fez o truque.

Isso é específico do nocaute, não da pergunta do OP, mas talvez ajude alguém a navegar aqui em uma situação semelhante.

domoarigato
fonte
Esta resposta não está relacionada
fauverism
apenas tangencialmente, como negada :)
domoarigato