Слабка (==) проти суворої (===) рівності в JavaScript
Сувора рівність (===) перевіряє значення і тип без жодних перетворень. Слабка рівність (==) спочатку перетворює операнди до спільного типу, а потім порівнює результати.
Теорія
TL;DR
===— як підібрати взуття точно за розміром і кольором;==— як розтягнути його під будь-яку ногу===не виконує жодних перетворень типів;==запускає алгоритм Abstract Equality Comparison5 === "5"дає false;5 == "5"дає true (рядок стає числом)null == undefinedдає true за специфікацією;null === undefined— false- Правило: скрізь використовуй
===;==прийнятна лише для перевіркиnull/undefined
Короткий приклад
console.log(5 === 5); // true
console.log(5 === "5"); // false - number vs string, без конвертації
console.log(5 == "5"); // true - "5" перетворюється на 5
console.log(true === 1); // false - boolean vs number
console.log(true == 1); // true - true перетворюється на 1
console.log(null == undefined); // true - спеціальне правило специфікації
console.log(null === undefined);// false - різні типи=== зупиняється на перевірці типу. == продовжує і конвертує.
Головна різниця
=== працює за алгоритмом SameValue: спочатку порівнює теги типів, потім значення. Без перетворень, без сюрпризів. == запускає Abstract Equality Comparison, який перетворює рядки на числа, булеві значення на числа (true в 1, false в 0) і викликає valueOf/toString для об'єктів. Саме через цей ланцюжок [] == 0 дає true: [] викликає toString() і отримує "", а потім "" перетворюється на 0 через ToNumber.
Коли що використовувати
- Будь-яке звичайне порівняння: використовуй
=== - Перевірка на
nullабоundefinedодночасно:value == nullловить обидва варіанти одним рядком - Порівняння введення користувача з числом після
parseInt: явно через=== - Застарілий код, де приведення типів задокументоване навмисно:
==з коментарем
Таблиця порівняння
| Аспект | === (Сувора) | == (Слабка) |
|---|---|---|
| Перевірка типу | Так, тип + значення | Ні, спочатку конвертує |
| Приведення типів | Відсутнє | Автоматичне (рядок в число тощо) |
| Передбачуваність | Висока | Низька |
| Продуктивність | Трохи швидше | Трохи повільніше |
5 == "5" | false | true |
null == undefined | false | true |
| Коли використовувати | Всі порівняння | Тільки value == null |
Як це працює всередині
V8 реалізує === як пряме порівняння тегів типів із подальшою перевіркою значень. Ніяких алокацій, ніяких перетворень. Для == рушій запускає ToPrimitive для об'єктів (спочатку valueOf, потім toString), а потім ToNumber для рядків. Ланцюжок перетворень може складатися з трьох кроків перед тим, як отримати фінальний результат.
Типові помилки
Використання == там, де очікується один конкретний тип:
// Неправильно - "0" і [] теж відповідатимуть умові
if (userInput == 0) { ... }
// Правильно
if (userInput === 0) { ... }Перевірка порожнього масиву через ==:
// Неправильно - ніколи не буде true, посилання різні
if (data == []) processData();
// Правильно
if (data.length === 0) processData();NaN ніколи не дорівнює самому собі:
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false - те саме правило
// Правильна перевірка
Number.isNaN(value);Умова циклу зі строковим введенням:
// Неправильно - рядок "1" відповідає умові, цикл виконується несподівано
while (count == 1) count--;
// Правильно
while (count === 1) count--;Де зустрічається в реальному коді
- React:
props.id === expectedIdу захисті компонентів - Express:
req.params.id === 'new'в обробниках маршрутів - Node.js:
process.env.NODE_ENV === 'production' - Redux:
action.type === types.FETCH_SUCCESS - Правило ESLint
eqeqeqавтоматично примушує використовувати===у кодовій базі
Питання на співбесіді
Q: Чому [] == 0 дає true?
A: [] викликає toString() і отримує "", потім "" перетворюється на 0 через ToNumber. Тому [] == "" і "" == 0, звідси [] == 0 — true.
Q: null == undefined — це true? Чому?
A: Так, це спеціальний випадок в Abstract Equality Comparison за специфікацією. Тільки null і undefined дорівнюють одне одному через ==; жодне з них не дорівнює 0, false чи "".
Q: NaN === NaN — false. Як перевірити на NaN?
A: Використовуй Number.isNaN(value). Трюк x !== x теж спрацює, бо NaN — єдине значення, що не дорівнює самому собі. Object.is(value, NaN) також варіант.
Q: Чи є місце для == у продакшені?
A: Один випадок: if (value == null) ловить і null, і undefined одним рядком. Деякі команди свідомо використовують цей патерн. В усьому іншому === — безпечний вибір за замовчуванням.
Q: Що додає Object.is() порівняно з ===?
A: Два граничних випадки: Object.is(NaN, NaN) дає true (на відміну від ===), і Object.is(+0, -0) дає false (на відміну від ===). Для більшості коду === достатньо, але Object.is точно відповідає специфікації SameValue.
Приклади
Базова поведінка приведення типів
// Сувора рівність - типи повинні збігатися
console.log(5 === 5); // true
console.log(5 === "5"); // false
console.log(false === 0); // false
// Слабка рівність - приведення типів у дії
console.log(5 == "5"); // true - рядок стає числом
console.log(false == 0); // true - false стає 0
console.log("" == 0); // true - порожній рядок стає 0
console.log("0" == false); // true - обидва стають 0== порівнює без конвертації лише тоді, коли обидві сторони вже мають однаковий тип.
Патерн у React-компоненті
function UserCard({ userId, isActive }) {
// Сувора перевірка - "0" не сприймається як відсутній пропс
if (userId === undefined) return <div>No user</div>;
// Безпечна перевірка через == - ловить і null, і undefined
if (isActive == null) return <div>Status unknown</div>;
return <div>User {userId} is {isActive ? "active" : "inactive"}</div>;
}
// Патерн з process.env - завжди сувора рівність
const isProd = process.env.NODE_ENV === "production";Я сам почав суворо дотримуватись === після того, як витратив час на відлагодження циклу, який виконувався зайвий раз: API повертав рядок "1" замість числа 1. Без === це не видно одразу.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.