Объясните поведение следующих выражений в JavaScript и причины: 0 == '0', 0 === '0', [] == ![], null == undefined — почему такие сравнения вызывают путаницу и какие практики сравнения рекомендуется использовать
Результаты и почему (сжатo, с ключевыми шагами преобразований JS): 1) 0==′0′ \;0 == '0'\;0==′0′ — true. Алгоритм: при абстрактном равенстве, если типы разные и один — строка, строка приводится к числу: ′0′→0'0' \to 0′0′→0. Сравниваем 0==00 == 00==0 → true. 2) 0===′0′ \;0 === '0'\;0===′0′ — false. Строгое равенство (=ˉ=\====ˉ=) не делает приведения типов: разные типы (Number vs String) → сразу false. 3) []==![] \;[] == ![]\;[]==![] — true (удивляет). Разбор: - Сначала вычисляется ![]![]![]. Пустой массив — truthy, значит ![]→false![] \to false![]→false. - Получаем []==false[] == false[]==false. - При сравнении Boolean с другим типом булевое значение приводится к числу: false→0false \to 0false→0. Значит []==0[] == 0[]==0. - Объект (массив) приводится к примитиву: пустой массив даёт строку ′′''′′ (пустая строка). Затем строка приводится к числу: ′′→0'' \to 0′′→0. - Итого 0==00 == 00==0 → true. 4) null==undefined \;null == undefined\;null==undefined — true. Специальное правило в спецификации: при абстрактном равенстве null и undefined считаются равными друг другу и только друг другу. Другие сравнения с null (например null==0null == 0null==0) дают false. Почему это вводит в заблуждение: - Операция =ˉ\===ˉ делает много неочевидных неявных приводений (ToPrimitive, ToNumber, ToString, ToBoolean) по разным правилам в зависимости от типов; есть специальные исключения (null/undefined). Это даёт множество сюрпризов (пример с []==![][] == ![][]==![], null==0\;null == 0null==0 и т.д.). Рекомендуемые практики: - Используйте строгое сравнение =ˉ=\====ˉ= и ==\!==== по умолчанию. - Для явных преобразований типов используйте Number(), String(), Boolean() перед сравнением, если нужно (не полагайтесь на неявное приведение). - Если нужно проверить «null или undefined», можно явно писать (x===null ∣∣ x===undefined)(x === null \,||\, x === undefined)(x===null∣∣x===undefined) или намеренно использовать (x==null)(x == null)(x==null) как идиому, но документируйте это правило и включите правило lint (eslint rule eqeqeq) с нужными исключениями. - Для особых случаев сравнения (NaN, -0/+0) используйте Object.is: Object.is(NaN,NaN)\text{Object.is}(NaN, NaN)Object.is(NaN,NaN) → true, Object.is(−0,+0)\text{Object.is}(-0, +0)Object.is(−0,+0) → false. Кратко: избегайте =ˉ\===ˉ без крайней необходимости — предпочитайте явные преобразования и =ˉ=\====ˉ=.
1) 0==′0′ \;0 == '0'\;0==′0′ — true.
Алгоритм: при абстрактном равенстве, если типы разные и один — строка, строка приводится к числу: ′0′→0'0' \to 0′0′→0. Сравниваем 0==00 == 00==0 → true.
2) 0===′0′ \;0 === '0'\;0===′0′ — false.
Строгое равенство (=ˉ=\====ˉ=) не делает приведения типов: разные типы (Number vs String) → сразу false.
3) []==![] \;[] == ![]\;[]==![] — true (удивляет). Разбор:
- Сначала вычисляется ![]![]![]. Пустой массив — truthy, значит ![]→false![] \to false![]→false.
- Получаем []==false[] == false[]==false.
- При сравнении Boolean с другим типом булевое значение приводится к числу: false→0false \to 0false→0. Значит []==0[] == 0[]==0.
- Объект (массив) приводится к примитиву: пустой массив даёт строку ′′''′′ (пустая строка). Затем строка приводится к числу: ′′→0'' \to 0′′→0.
- Итого 0==00 == 00==0 → true.
4) null==undefined \;null == undefined\;null==undefined — true.
Специальное правило в спецификации: при абстрактном равенстве null и undefined считаются равными друг другу и только друг другу. Другие сравнения с null (например null==0null == 0null==0) дают false.
Почему это вводит в заблуждение:
- Операция =ˉ\===ˉ делает много неочевидных неявных приводений (ToPrimitive, ToNumber, ToString, ToBoolean) по разным правилам в зависимости от типов; есть специальные исключения (null/undefined). Это даёт множество сюрпризов (пример с []==![][] == ![][]==![], null==0\;null == 0null==0 и т.д.).
Рекомендуемые практики:
- Используйте строгое сравнение =ˉ=\====ˉ= и ==\!==== по умолчанию.
- Для явных преобразований типов используйте Number(), String(), Boolean() перед сравнением, если нужно (не полагайтесь на неявное приведение).
- Если нужно проверить «null или undefined», можно явно писать (x===null ∣∣ x===undefined)(x === null \,||\, x === undefined)(x===null∣∣x===undefined) или намеренно использовать (x==null)(x == null)(x==null) как идиому, но документируйте это правило и включите правило lint (eslint rule eqeqeq) с нужными исключениями.
- Для особых случаев сравнения (NaN, -0/+0) используйте Object.is: Object.is(NaN,NaN)\text{Object.is}(NaN, NaN)Object.is(NaN,NaN) → true, Object.is(−0,+0)\text{Object.is}(-0, +0)Object.is(−0,+0) → false.
Кратко: избегайте =ˉ\===ˉ без крайней необходимости — предпочитайте явные преобразования и =ˉ=\====ˉ=.