Порожні масиви здаються одночасно істинними та хибними


201

Порожні масиви є правдивими, але вони також рівні хибним.

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

Я думаю, це пов'язано з неявним перетворенням, керованим оператором рівності.

Хтось може пояснити, що відбувається за лаштунками?


1
Ось подібна нитка, яка повинна пролити трохи світла щодо цього питання: stackoverflow.com/questions/4226101/…
Rion Williams

2
Зауважте, що arr == trueце не відповідає дійсності ;-)
Майкл Крелін - хакер

5
Вау ... просто тоді, коли ви думали, що все це у вас є.
harpo

3
Щоб уникнути примусу WTF типу Javascript, використовуйте сувору функціональну перевірку рівності ===. Тоді, якщо ви хочете перевірити порожнечу масиву, використовуйтеarr === []
DjebbZ

17
Якщо ви хочете перевірити порожнечу масиву, НЕ використовуйте arr === [], оскільки це ЗАВЖДИ поверне помилкове значення, оскільки права сторона інстанціює новий масив, а змінна зліва не може посилатися на те, що ви тільки що створили. Тестування порожнечі слід проводити, дивлячись вгору arr.length === 0.
Кайл Бейкер

Відповіді:


274

Ви випробовуєте тут різні речі.

if (arr) викликаний об'єкт (масив є екземпляром Object в JS) перевірить, чи є об'єкт присутній, і поверне true / false.

Під час дзвінка if (arr == false)ви порівнюєте значення цього об'єкта та примітивне falseзначення. Внутрішньо arr.toString()називається, який повертає порожню рядок "".

Це тому, що toStringвикликається повернення масиву Array.join(), а порожня рядок - одне з фальшивих значень у JavaScript.


2
Чи можете ви пояснити, чому Boolean([])повертається true?
Devy

11
це за умовою, в JS, якщо об'єкти примусові до булевих, вони завжди примусові до ІСТИНИ. дивіться таблицю "Булевого контексту" за адресою: javascript.info/tutorial/object-conversion
Niki

2
@Devy всі об'єкти в JavaScript є truthy, тому перетворення будь-яких об'єктів у Boolean є істинним. Дивіться 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. Якщо Type (x) відрізняється від Type (y), перейдіть до кроку 14.

Наступне правило, яке застосовується - це №19:

19. Якщо Type (y) булевий, поверніть результат порівняння x == ToNumber (y).

Результатом ToNumber(false)є 0, тож тепер у нас є:

[] == 0

Знову-таки, правило №1 підказує нам перейти до кроку №14, але наступний крок, який насправді застосовується, - це №21:

21. Якщо Type (x) є Object, а Type (y) або String або Number, поверніть результат порівняння ToPrimitive (x) == y.

Результат ToPrimitive([])- порожній рядок, тому у нас є:

"" == 0

Знову-таки, правило №1 підказує нам перейти до кроку №14, але наступний крок, який насправді застосовується, - це №17:

17. Якщо Type (x) є String, а Type (y) - Number, поверніть результат порівняння ToNumber (x) == y.

Результат ToNumber("")IS 0, який залишає нам:

0 == 0

Тепер обидва значення мають один і той же тип, тому дії продовжуються від №1 до №7, де написано:

7. Якщо x - те саме значення числа, що і y, поверніть true.

Отже, ми повертаємось true.

Коротко:

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

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. Щоб спростити справу, розпочавшись з деякими деталями, якщо натяк є String, то [[DefaultValue]]повертає значення, toString()якщо воно існує, і повертає примітивне значення, а в іншому випадку повертає значення valueOf(). Якщо натяк - Число, пріоритети toString()і valueOf()змінюються таким чином, що valueOf()називається першим, а його значення повертається, якщо це примітив. Таким чином, [[DefaultValue]]повертає результат toString()чиvalueOf() залежить від зазначеного PreferredType для об'єкта та повертають чи ні ці функції примітивні значення.

Метод valueOf()Object за замовчуванням просто повертає сам об’єкт, що означає, що якщо клас не перекриє метод за замовчуванням, він valueOf()просто повертає сам Object. Це справа для 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

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


Дякую за цю відповідь, це досить детальне пояснення, що питання бракувало.
колба Естуса

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 є truthy . (null не є об’єктом!)

[] == falseоцінюється в ітеративному підході. Спочатку, якщо одна сторона елемента ==примітивна, а інша - об'єктна, вона спочатку перетворює об'єкт в примітивну, потім перетворює обидві сторони в число, якщо обидві сторони не є string(порівняння рядків використовується, якщо обидві сторони є рядками). Тож порівняння повторюється як, [] == false-> '' == false-> 0 == 0-> true.


2

Приклад:

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

Це буває тому, що

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []порожній Arrayоб’єкт → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → вірно

1

Масив з елементами (незалежно від того, 0, помилковий чи інший порожній масив) завжди вирішує 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

-1

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

Джерело: Порожній масив JavaScript


-2

Ніщо з вищезазначеного не допомогло мені, намагаючись використовувати плагін mapck.js mapping, можливо, оскільки "порожній масив" насправді не порожній.

Я закінчив використовувати: data-bind="if: arr().length"що зробив трюк.

Це характерно для нокауту, а не до питання ОП, але, можливо, це допоможе комусь іншому переглядати тут у подібній ситуації.


Ця відповідь не пов'язана
фауризм

тільки тангенціально, як відкидають :)
domoarigato
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.