Пустые массивы кажутся равными истинному и ложному одновременно

201

Пустые массивы имеют значение true, но они также равны false.

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

Я предполагаю, что это связано с неявным преобразованием, используемым оператором равенства.

Кто-нибудь может объяснить, что происходит за кулисами?

Patonza
источник
1
Вот аналогичная ветка, которая должна пролить свет на проблему: stackoverflow.com/questions/4226101/…
Рион Уильямс
2
Обратите внимание, что arr == trueэто не соответствует действительности ;-)
Майкл Крелин - хакер
5
Ух ты ... только когда ты думал, что у тебя все это есть.
Harpo
3
Чтобы избежать приведения типа Javascript WTF, используйте оператор строгого равенства ===. Тогда, если вы хотите проверить пустоту массива, используйтеarr === []
DjebbZ
17
Если вы хотите проверить пустоту массива, НЕ используйте arr === [], так как это ВСЕГДА будет возвращать false, поскольку правая сторона создает новый массив, а переменная слева не может ссылаться на то, что вы только что создали. Проверка пустоты должна быть сделана, глядя вверх arr.length === 0.
Кайл Бейкер,

Ответы:

274

Здесь вы тестируете разные вещи.

if (arr) Вызванный объект (Array является экземпляром Object в JS) проверит наличие объекта и вернет true / false.

При вызове if (arr == false)вы сравниваете значения этого объекта и falseзначения примитива . Внутренне arr.toString()вызывается, что возвращает пустую строку "".

Это вызвано тем, что вызывается toStringпри возврате Array Array.join(), а пустая строка является одним из ложных значений в JavaScript.

подстановочные
источник
2
Можете ли вы объяснить, почему Boolean([])возвращается true?
Деви
11
это условно, в JS, если объекты приводятся к Boolean, они всегда приводятся к TRUE. посмотрите на таблицу «логический контекст» по адресу: javascript.info/tutorial/object-conversion
Ники,
2
@Devy все объекты в JavaScript являются правдивыми, поэтому преобразование любых объектов в логические значения является истинным. См. 2ality.com/2013/08/objects-truthy.html
Томсон
62

Что касается линии:

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

Может быть, это поможет:

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

Я считаю, что происходит то, что булево значение falseприводится 0для сравнения с объектом (левая часть). Объект приводится к строке (пустой строке). Затем пустая строка также приводится к числу, а именно к нулю. И вот окончательное сравнение 0== 0, чтоtrue .

Изменить: см. Этот раздел спецификации для деталей о том, как именно это работает.

Вот что происходит, начиная с правила № 1:

1. Если Тип (x) отличается от Типа (y), перейдите к шагу 14.

Следующее правило, которое применяется, это # ​​19:

19. Если тип (y) является логическим, вернуть результат сравнения x == ToNumber (y).

Результатом ToNumber(false)является 0, таким образом , мы теперь имеем:

[] == 0

Опять же, правило № 1 говорит нам перейти к шагу № 14, но следующий действительный шаг - № 21:

21. Если тип (x) равен объекту, а тип (y) равен либо строковому, либо числовому, возвращает результат сравнения ToPrimitive (x) == y.

Результатом ToPrimitive([])является пустая строка, поэтому мы теперь имеем:

"" == 0

Опять же, правило № 1 говорит нам перейти к шагу № 14, но следующий действительный шаг - № 17:

17. Если Type (x) равен String, а Type (y) равен Number, вернуть результат сравнения ToNumber (x) == y.

Результатом ToNumber("")является то 0, что оставляет нас с:

0 == 0

Теперь оба значения имеют одинаковый тип, поэтому шаги продолжаются с # 1 до # 7, что говорит:

7. Если x совпадает с y, верните true.

Итак, мы вернемся true.

Вкратце:

ToNumber(ToPrimitive([])) == ToNumber(false)
Wayne
источник
2
Отличная ссылка! Чтобы избежать путаницы, было бы полезно упомянуть, что причина «следующее правило, которое применяется # 19», хотя правило № 1 говорит «перейти к шагу 14», заключается в том, что шаги 14-18 не соответствуют типам сравниваемые значения
Шон Боб
2
Хорошее объяснение. Меня удивляет, что пустые массивы считаются правдивыми, 0 ложными, и все [] == 0же верно. Я понимаю, как это происходит, основываясь на вашем объяснении спецификации, но это кажется странным поведением языка с логической точки зрения.
bigh_29
7

Чтобы дополнить ответ Уэйна и попытаться объяснить, почему ToPrimitive([])возвращается "", стоит рассмотреть два возможных типа ответов на вопрос «почему». Первый тип ответа: «потому что в спецификации сказано, что именно так будет вести себя JavaScript». В спецификации ES5, раздел 9.1 , который описывает результат ToPrimitive в качестве значения по умолчанию для объекта:

Значение объекта по умолчанию извлекается путем вызова внутреннего метода [[DefaultValue]] объекта, передавая необязательную подсказку PreferredType.

Раздел 8.12.8 описывает [[DefaultValue]]метод. Этот метод принимает «подсказку» в качестве аргумента, и подсказка может быть либо String, либо Number. Чтобы упростить этот вопрос, отказавшись от некоторых деталей, если подсказкой является строка, то [[DefaultValue]]возвращается значение, toString()если оно существует, и возвращается примитивное значение, а в противном случае возвращается значение valueOf(). Если подсказка является Number, приоритеты toString()и valueOf()меняются местами, так что valueOf()сначала вызывается и возвращается его значение, если это примитив. Таким образом, [[DefaultValue]]возвращает ли результат результат toString()или valueOf()зависит от указанного PreferredType для объекта и возвращают ли эти функции примитивные значения.

Метод valueOf()Object по умолчанию просто возвращает сам объект, а это означает, что если класс не переопределяет метод по умолчанию, он valueOf()просто возвращает сам объект. Это дело для Array. [].valueOf()возвращает сам объект []. Поскольку Arrayобъект не является примитивом, [[DefaultValue]]подсказка не имеет значения: возвращаемое значение для массива будет значением toString().

Процитируем JavaScript Дэвида Фланагана : «Полное руководство» , которое, кстати, является превосходной книгой, которая должна стать первым местом для всех, чтобы получить ответы на следующие вопросы:

Детали этого преобразования объекта в число объясняют, почему пустой массив преобразуется в число 0 и почему массив с одним элементом также может преобразовываться в число. Массивы наследуют метод valueOf () по умолчанию, который возвращает объект, а не примитивное значение, поэтому преобразование массива в число полагается на метод toString (). Пустые массивы преобразуются в пустую строку. И пустая строка преобразуется в число 0. Массив с одним элементом преобразуется в ту же строку, что и этот элемент. Если массив содержит одно число, это число преобразуется в строку, а затем обратно в число.

Второй тип ответа на вопрос «почему», отличный от «потому что спецификация говорит», дает некоторое объяснение того, почему поведение имеет смысл с точки зрения дизайна. По этому вопросу я могу только строить догадки. Во-первых, как преобразовать массив в число? Единственная разумная возможность, о которой я могу подумать, это преобразовать пустой массив в 0, а любой непустой массив в 1. Но, как показал ответ Уэйна, пустой массив будет преобразован в 0 для многих типов сравнений в любом случае. Помимо этого, трудно придумать разумное примитивное возвращаемое значение для Array.valueOf (). Таким образом, можно утверждать, что имеет больше смысла иметь Array.valueOf()значение по умолчанию и возвращать сам массив, что приводит toString()к результату, используемому ToPrimitive. Просто имеет смысл преобразовать массив в строку, а не в число.

Более того, как подсказывает цитата Фланагана, это дизайнерское решение действительно допускает определенные типы полезного поведения. Например:

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

Такое поведение позволяет сравнивать одноэлементный массив с числами и получать ожидаемый результат.

CJG
источник
Спасибо за этот ответ, это довольно подробное объяснение, которого не хватало в вопросе.
Estus Колба
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

В if (arr) он всегда оценивается (ToBoolean) как true, если arr является объектом, потому что все объекты в JavaScript верны . (ноль не объект!)

[] == falseоценивается в итеративном подходе. Сначала, если одна сторона ==является примитивной, а другая - объектом, сначала она преобразует объект в примитив, а затем преобразует обе стороны в число, если не обе стороны string(сравнение строк используется, если обе стороны являются строками). Таким образом, сравнение повторяется как, [] == false-> '' == false-> 0 == 0-> true.

Thomson
источник
2

Пример:

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

Это происходит потому что

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []пустой Arrayобъект → ToPrimitive([])→ "" →ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → верно
yqbk
источник
1

Массив с элементами (независимо от 0, false или другого пустого массива) всегда разрешается с trueиспользованием абстрактного сравнения равенства ==.

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

Но, используя Сравнение строгого равенства ===, вы пытаетесь оценить содержимое переменной, а также ее тип данных, поэтому:

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
источник
-1

Вы можете очистить массив JavaScript, ссылаясь на него в новом массиве, используя list = []или удаляя элементы текущего массива, на который ссылаются list.length = 0.

Источник: JavaScript Пустой массив

AliveXhd
источник
-2

Ничто из вышеперечисленного не помогло мне при попытке использовать плагин отображения knockout.js, возможно, так как «пустой массив» на самом деле не пустой.

Я закончил тем, что использовал: data-bind="if: arr().length"который добился цели.

Это относится только к нокауту, а не к вопросу ОП, но, возможно, это поможет кому-то еще, находящемуся здесь в аналогичной ситуации.

domoarigato
источник
Этот ответ не имеет отношения
фоверизм
только косвенно, как
отказано