Las matrices vacías parecen ser iguales a verdadero y falso al mismo tiempo


201

Las matrices vacías son verdaderas, pero también son iguales 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??");

Supongo que esto se debe a la conversión implícita operada por el operador de igualdad.

¿Alguien puede explicar lo que sucede detrás de escena?


1
Aquí hay un hilo similar que debería arrojar algo de luz sobre el tema: stackoverflow.com/questions/4226101/…
Rion Williams

2
Tenga en cuenta que eso arr == trueno se evalúa como verdadero ;-)
Michael Krelin - hacker

55
Wow ... justo cuando pensabas que tenías todo esto abajo.
harpo

3
Para evitar la coacción de tipo Javascript WTF, use el operador de igualdad estricta ===. Entonces, si desea probar el vacío de una matriz, usearr === []
DjebbZ

17
Si desea probar el vacío de una matriz, NO la use arr === [], ya que eso SIEMPRE devolverá falso, ya que el lado derecho está instanciando una nueva matriz, y la variable de la izquierda no puede referirse a algo que acaba de crear. La prueba del vacío debe hacerse mirando hacia arriba arr.length === 0.
Kyle Baker

Respuestas:


274

Estás probando cosas diferentes aquí.

if (arr) llamado en el objeto (Array es una instancia de Object en JS) verificará si el objeto está presente y devuelve verdadero / falso.

Cuando llama if (arr == false), compara los valores de este objeto y el falsevalor primitivo . Internamente, arr.toString()se llama, lo que devuelve una cadena vacía "".

Esto se debe a que se toStringllama a los retornos de matriz Array.join(), y la cadena vacía es uno de los valores falsos en JavaScript.


2
¿Puedes explicar por qué Boolean([])regresa true?
Devy

11
eso es por convención, en JS si los objetos se convierten en booleanos, siempre se convierten en VERDADERO. mire la tabla de "contexto booleano" en: javascript.info/tutorial/object-conversion
Niki

2
@Devy todos los objetos en JavaScript son verdaderos, por lo que la conversión de cualquier objeto a Boolean es verdadera. Ver 2ality.com/2013/08/objects-truthy.html
Thomson

62

En cuanto a la línea:

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

Quizás estos ayuden:

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

Lo que creo que está sucediendo es que el booleano falseestá obligado a 0compararlo con un objeto (el lado izquierdo). El objeto es forzado a una cadena (la cadena vacía). Luego, la cadena vacía se convierte en un número, también, es decir, cero. Y así, la comparación final es 0== 0, que es true.

Editar: consulte esta sección de la especificación para obtener detalles sobre cómo funciona exactamente.

Esto es lo que sucede, comenzando en la regla 1:

1. Si el Tipo (x) es diferente del Tipo (y), vaya al paso 14.

La siguiente regla que aplica es la # 19:

19. Si Tipo (y) es booleano, devuelve el resultado de la comparación x == ToNumber (y).

El resultado de ToNumber(false)es 0, así que ahora tenemos:

[] == 0

Nuevamente, la regla # 1 nos dice que saltemos al paso # 14, pero el siguiente paso que realmente aplica es el # 21:

21. Si Tipo (x) es Objeto y Tipo (y) es Cadena o Número, devuelve el resultado de la comparación ToPrimitive (x) == y.

El resultado de ToPrimitive([])es la cadena vacía, por lo que ahora tenemos:

"" == 0

Nuevamente, la regla n. ° 1 nos dice que saltemos al paso n. ° 14, pero el siguiente paso que realmente se aplica es el n. ° 17:

17. Si Type (x) es String y Type (y) es Number, devuelve el resultado de la comparación ToNumber (x) == y.

El resultado de ToNumber("")es 0, lo que nos deja con:

0 == 0

Ahora, ambos valores tienen el mismo tipo, por lo que los pasos continúan desde el # 1 hasta el # 7, que dice:

7. Si x es el mismo valor numérico que y, devuelve verdadero.

Entonces volvemos true.

En breve:

ToNumber(ToPrimitive([])) == ToNumber(false)

2
Gran referencia! Para evitar confusiones, puede ser útil mencionar que la razón "la siguiente regla que aplica es la # 19" a pesar de que la regla # 1 dice "vaya al paso 14", es porque los pasos 14-18 no coinciden con los tipos de valores que se comparan.
Sean the Bean

2
Buena explicación Me resulta desconcertante que las matrices vacías se consideren verdaderas, 0 es falsey y, sin embargo, [] == 0es cierto. Entiendo cómo sucede esto en función de su explicación de la especificación, pero parece un comportamiento extraño del lenguaje desde un punto de vista lógico.
bigh_29

7

Para complementar la respuesta de Wayne y tratar de explicar por qué ToPrimitive([])regresa "", vale la pena considerar dos posibles tipos de respuestas a la pregunta 'por qué'. El primer tipo de respuesta es: "porque la especificación dice que así es como se comportará JavaScript". En la especificación ES5, sección 9.1 , que describe el resultado de ToPrimitive como valor predeterminado para un objeto:

El valor predeterminado de un objeto se recupera llamando al método interno [[DefaultValue]] del objeto, pasando la sugerencia opcional PreferredType.

La Sección 8.12.8 describe el [[DefaultValue]]método. Este método toma una "pista" como argumento, y la pista puede ser String o Number. Para simplificar el asunto prescindiendo de algunos detalles, si la pista es String, [[DefaultValue]]devuelve el valor de toString()si existe y devuelve un valor primitivo y, de lo contrario, devuelve el valor de valueOf(). Si la sugerencia es Número, las prioridades de toString()y valueOf()se invierten, por lo que valueOf()se llama primero y se devuelve su valor si es un primitivo. Por lo tanto, si [[DefaultValue]]devuelve el resultado toString()o valueOf()depende del PreferredType especificado para el objeto y si estas funciones devuelven o no valores primitivos.

El valueOf()método Object predeterminado solo devuelve el objeto en sí, lo que significa que a menos que una clase anule el método predeterminado, valueOf()solo devuelve el Object en sí. Este es el caso para Array. [].valueOf()devuelve el objeto en []sí. Como un Arrayobjeto no es primitivo, la [[DefaultValue]]sugerencia es irrelevante: el valor de retorno para una matriz será el valor de toString().

Para citar el JavaScript de David Flanagan : The Definitive Guide , que, por cierto, es un libro excelente que debería ser el primer lugar de todos para obtener respuestas a este tipo de preguntas:

Los detalles de esta conversión de objeto a número explican por qué una matriz vacía se convierte en el número 0 y por qué una matriz con un solo elemento también puede convertirse en un número. Las matrices heredan el método predeterminado valueOf () que devuelve un objeto en lugar de un valor primitivo, por lo que la conversión de matriz a número se basa en el método toString (). Las matrices vacías se convierten en la cadena vacía. Y la cadena vacía se convierte en el número 0. Una matriz con un solo elemento se convierte en la misma cadena que ese elemento. Si una matriz contiene un solo número, ese número se convierte en una cadena y luego vuelve a un número.

El segundo tipo de respuesta a la pregunta "por qué", que no sea "porque la especificación dice", da una explicación de por qué el comportamiento tiene sentido desde la perspectiva del diseño. Sobre este tema solo puedo especular. Primero, ¿cómo se convertiría una matriz en un número? La única posibilidad sensata que se me ocurre sería convertir una matriz vacía a 0 y cualquier matriz no vacía a 1. Pero como reveló la respuesta de Wayne, una matriz vacía se convertirá a 0 para muchos tipos de comparaciones de todos modos. Más allá de esto, es difícil pensar en un valor de retorno primitivo sensible para Array.valueOf (). Por lo tanto, se podría argumentar que tiene más sentido tener Array.valueOf()el valor predeterminado y devolver la matriz en sí, lo toString()que lleva a ser el resultado utilizado por ToPrimitive. Simplemente tiene más sentido convertir una matriz en una cadena, en lugar de un número.

Además, como lo sugiere la cita de Flanagan, esta decisión de diseño permite ciertos tipos de comportamientos beneficiosos. Por ejemplo:

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

Este comportamiento le permite comparar una matriz de un solo elemento con números y obtener el resultado esperado.


Gracias por esta respuesta, esa es una explicación bastante detallada de la que ha faltado la pregunta.
Estus Flas

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

2

En if (arr), siempre se evalúa (ToBoolean) como verdadero si arr es un objeto porque todos los objetos en JavaScript son verdaderos . (nulo no es un objeto!)

[] == falsese evalúa en enfoque iterativo. Al principio, si un lado de ==es primitivo y el otro es objeto, primero convierte el objeto en primitivo, luego convierte ambos lados en Número si ambos lados no lo son string(la comparación de cadenas se usa si ambos lados son cadenas). Entonces la comparación se repite como, [] == false-> '' == false-> 0 == 0-> true.


2

Ejemplo:

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

Sucede porque

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []es un Arrayobjeto vacío → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → verdadero

1

Una matriz con elementos (independientemente de si 0, falso u otra matriz vacía), siempre resuelve trueusar la Comparación de igualdad abstracta ==.

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

Pero utilizando una comparación estricta de igualdad ===, está intentando evaluar el contenido de la variable, así como su tipo de datos, por eso:

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

-1

Puede vaciar una matriz de JavaScript haciendo referencia a una nueva matriz, utilizando list = []o eliminando los elementos de la matriz referenciada actualmente list.length = 0.

Fuente: JavaScript Empty Array


-2

Nada de lo anterior me ayudó, cuando traté de usar el complemento de mapeo knockout.js, tal vez porque una "matriz vacía" no está realmente vacía.

Terminé usando: data-bind="if: arr().length"que hizo el truco.

Esto es específico del nocaut, no es la pregunta del OP, pero tal vez ayude a alguien más a navegar aquí en una situación similar.


Esta respuesta no está relacionada
fauverismo

solo tangencialmente, como renunciado :)
domoarigato
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.