Чому [] == false повертає true
[] == false дає true тому, що під час нестрогого порівняння JavaScript автоматично приводить обидві сторони до числа і врешті порівнює 0 == 0. Масив по дорозі перетворюється на порожній рядок, а порожній рядок на нуль, тож рівність виявляється правдивою, хоча жодної змістовної рівності тут немає.
Теорія
TL;DR
==запускає приведення типів (type coercion),===не запускає.- Булеве значення в порівнянні
==завжди спершу стає числом:falseце0. - Об'єкт (а масив теж об'єкт) приводиться до примітиву через
valueOf(), потімtoString(). [].toString()дає порожній рядок'', аNumber('')дає0.- Тому вираз зводиться до
0 == 0, тобтоtrue. [] === falseдаєfalse, бо строге порівняння не робить приведення.
Швидкий приклад
console.log([] == false); // true
console.log([] === false); // false
// Той самий ланцюжок, розписаний уручну:
console.log(Number(false)); // 0
console.log([].toString()); // ''
console.log(Number('')); // 0
console.log(0 == 0); // trueПокрокове пояснення
Візьмімо вираз:
[] == falseКрок 1. == запускає type coercion. При нестрогому порівнянні JS намагається привести типи так, щоб порівняння стало можливим. Алгоритм абстрактної рівності в специфікації ECMAScript описує це правилом:
Якщо один з операндів має тип
boolean, він приводиться до числа.
Крок 2. Приводимо false до числа.
false -> 0Тепер вираз виглядає так:
[] == 0Крок 3. Ліворуч об'єкт (масив), праворуч примітив. Якщо один з операндів об'єкт, він спершу приводиться до примітиву (через valueOf(), а потім toString()). Для масиву []:
[].toString(); // ''Отже:
[] -> '' // порожній рядокТепер маємо:
'' == 0Крок 4. Порівняння '' == 0. За тим самим алгоритмом:
Якщо один операнд рядок, а другий число, то рядок перетворюється на число.
Number('') -> 0Отже:
0 == 0Результат:
trueПідсумок по кроках
| Етап | Вираз | Що сталося |
|---|---|---|
| 1 | [] == false | false -> 0 |
| 2 | [] == 0 | [] -> '' |
| 3 | '' == 0 | '' -> 0 |
| 4 | 0 == 0 | true |
Чому це небезпечно
Ця логіка робить == непередбачуваним. Ось іще приклади того самого механізму:
[] == ![] // true
'' == 0 // true
'0' == 0 // true
null == undefined // true
[] == '' // true
[0] == 0 // true
[1] == 1 // true
[2] == 2 // trueОсобливо показовий перший рядок: ![] це false (масив завжди truthy, тому його заперечення дає false), далі працює вже знайомий ланцюжок і [] == ![] теж стає true. Тобто значення дорівнює власному запереченню, і це цілком легальний JavaScript.
Як уникнути плутанини
Використовуйте строге порівняння ===, яке не виконує неявного приведення типів.
[] === false // false
'' === 0 // false
null === undefined // falseКоротке зведення:
| Вираз | Що робить JS | Результат |
|---|---|---|
[] == false | [] -> '' -> 0, false -> 0 | true |
[] === false | Порівнює без приведення | false |
Якщо треба перевірити, чи масив порожній, перевіряйте це явно:
if (items.length === 0) {
// порожній масив
}Типові помилки
- Перевіряти порожність масиву через порівняння з
falseабо0. Працює випадково і ламається, щойно значення станеnullабо об'єктом. Перевіряйтеitems.length === 0. - Вважати, що
[] == falseозначає «масив хибний». Насправді сам по собі[]truthy: уif ([])тіло виконається. Хибність тут виникає лише через приведення до числа в операторі==. - Плутати
==з===у перевірках наnull. Тут==іноді корисний:x == nullловить іnull, іundefined. Але це єдиний виняток, який варто застосовувати свідомо. - Покладатися на
valueOf()для масивів. Для звичайного масивуvalueOf()повертає сам масив, тобто не примітив, тому рушій переходить доtoString(). Якщо перевизначитиvalueOf(), результат порівняння зміниться. - Ігнорувати лінтер. Правило
eqeqeqу ESLint забороняє==саме через такі випадки, і вимикати його заради «коротшого запису» не варто.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.