Прочитав эту статью, вы будете знать все, что вам нужно знать о принуждении и операторе двойного равенства в JavaScript.
TL; DR: всегда используйте оператор тройного равенства и усваивайте алгоритмы приведения
Двойное равенство
Оператор двойного равенства приводит к приведению операндов, если их типы не совпадают, в противном случае он будет использовать оператор тройного равенства. Итак, 2 == 3
то же самое, что и 2 === 3
, потому что и 2, и 3 относятся к числовому типу. Но если типы не совпадают, JavaScript попытается принудить один или оба операнда. Например, если вы сравниваете 2 == '3'
, вы сравниваете номер 2 со строкой, значение которой равно 3. В этом случае JavaScript приведет к преобразованию строки 3 в номер 3, а затем вызовет 2 === 3
, что приведет к ложному результату.
Теперь, когда мы знаем основы оператора двойного равенства, давайте углубимся глубже и узнаем, как оператор решает, что делать.
Абстрактное сравнение равенства
Двойное равенство использует алгоритм абстрактного равенства, чтобы решить, как сравнивать значения. Предположим, что мы сравниваем x
и y
, где x
и y
могут быть любого типа. Вот как работает алгоритм:
Начало: проверьте, совпадают ли типы. Если типы совпадают, JavaScript вызовет тройное равенство, и все готово.
но, если типы не совпадают, JavaScript пойдет по другому пути. На этом пути JavaScript попытается выяснить, что принудить. Вот шаги, которые он будет выполнять по порядку:
1. Сначала проверьте, сравниваем ли мы null
с undefined
, то есть null == undefined
. Если да, верните true
. Если не:
2. Проверьте, сравниваем ли мы string
с number
. Если да, приведите строковое значение к числовому, вызовите двойное равенство и начните снова с самого первого шага. Например:
2 == "3" ↓ coerce "3" to number 3. 2 == 3 calling double equals again, and start over ...
но если нас нет:
3. Проверьте, сравниваем ли мы boolean
с чем-то другим. Если да, приведите логическое значение к числу, вызовите двойное равенство и начните заново. например:
true == "3" ↓ coerce true to number 1. 1 == "3" start over again. ...
но если мы не
4. Проверьте, сравниваем ли мы object
с number
, string
или symbol
. Если да, приведите объект к примитиву, вызовите двойное равенство для результата и начните заново. Например:
{ a: 'hello'} == "5" ↓ coerce the object to a primitive "[object Object]" == "5" and start over again ...
(Я объясню, как объекты преобразуются в примитивы, через секунду, но пока давайте разберемся со мной.)
И, наконец, если ни один из вышеперечисленных шагов не подходит, верните в конце false
.
Ниже представлена полная картина:
Абстрактные операторы
В этом разделе мы рассмотрим внутренние абстрактные операторы, которые JavaScript использует для приведения типов. Мы рассмотрим следующие абстрактные операторы:
- ToPrimitive
- ToNumber
- Нанизывать
- ToBoolean
Приведение объектов к примитивам
Я обещал вам объяснить, как объекты преобразуются в примитивы. Вот как это работает:
Сначала по умолчанию вызывается valueOf
метод объекта. Если valueOf
возвращает примитив, используйте его.
В противном случае вызовите toString
метод объекта. Если toString
возвращает примитив, используйте это
в противном случае выдает ошибку.
Примечание 1. Эти действия выполняются по умолчанию. Если намекают, что нужно предпочесть строку, JavaScript сначала вызовет toString
, а затем valueOf
. По умолчанию JavaScript предпочитает номер, поэтому сначала вызывается valueOf
, а затем toString
.
Примечание 2. При принуждении объекта Date
к примитиву JavaScript предпочитает строку. Таким образом, он сначала вызовет toString
, а затем valueOf
. Во всех остальных случаях подсказка предпочтительного типа по умолчанию - число.
Давайте рассмотрим несколько примеров и рассмотрим, как объекты приводятся к примитивам:
Пример 1:
// coercing an empty object to a primitive var x = {}; x.valueOf(); // -> {} : not a primitive, call toString now. x.toString(); // -> "[object Object]": string is a primitive, I like it!
В приведенном выше примере, когда x приводится к примитиву, результатом будет забавно выглядящая строка: “[object Object]”
Пример 2:
// coercing an object with custom `valueOf` var x = {name: 'Tom'}; x.valueOf = function () {return 5;}; x.valueOf(); // -> 5 : number primitive, I liked it!
В приведенном выше примере, когда x приводится к примитиву, результатом будет число 5.
Пример 3:
// coercing an array of numbers to a primitive var x = [1,2,3]; x.valueOf(); // -> [1,2,3] : not a primitive, call toString now. x.toString(); // -> "1,2,3" : string is a primitive, I like it!
В приведенном выше примере, когда x приводится к примитиву, результатом будет строка: “1,2,3”
Пример 4:
// coercing an array of elements with different types to a primitive var x = [undefined, null, true, 1, "5", new Date(), {a: 2}, [1,2,3]] x.valueOf(); // -> Array itself : not a primitive, call toString now. x.toString(); // -> ",,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3" : string is a primitive, I like it!
В приведенном выше примере, когда x приводится к примитиву, результатом будет строка: “,,true,1,5,Sat Mar 05 2016 10:59:47 GMT-0500 (EST),[object Object],1,2,3”
Приведение к номеру: ToNumber (ввод)
Ниже приводится сводная информация о том, что ToNumber
делает для различных типов ввода:
- undefined →
NaN
- ноль → 0
- число → вернуть само число
- логическое → 0, если ввод ложный, 1, если ввод истинный
- строка → строка синтаксического анализа, если строка является числом, вернуть число, в противном случае вернуть
NaN
. Пустая строка возвращает 0. - Eg: “5” → 5,
“”
→0
,“29xY”
→NaN
- объект: сначала вызовите
ToPrimitive
, затем вызовитеToNumber
по результатуToPrimitive
. См. РазделtoPrimitive
, чтобы узнать, как объекты преобразуются в примитивы. - Дата → вызывает метод
getTime()
и возвращает значение
Принуждение к строке: ToString (ввод)
Ниже приводится сводная информация о том, что ToNumber делает для разных типов ввода:
- undefined →
“undefined”
- null →
“null”
- номер →
“number”
- логическое →
“true”
,“false”
- строка → вернуть саму строку.
- объект: сначала вызовите
ToPrimitive
с подсказкой строки, затем вызовитеToString
для результатаToPrimitive
. См. Раздел toPrimitive, чтобы узнать, как объекты преобразуются в примитивы. - Дата → вызвать
toString()
и вернуть значение
Приведение к логическому: ToBoolean (ввод)
В JavaScript неверны следующие значения:
null, undefined, '', 0, false
Все остальное правда. Знание этого факта позволит легко узнать, что происходит после того, как значение приводится к логическому. В основном все ложные значения приводятся к false
, а все остальное - к true
.
- undefined → ложь
- ноль → ложь
- число → ложь, если 0, в противном случае истина
- логическое → ввод
- строка → ложь, если пустая строка «», в противном случае истина
- объект → истина. Отсюда следует, что array → true
- Дата → true
Примеры двойного равенства
Теперь, когда мы знаем об алгоритме абстрактного равенства, давайте рассмотрим несколько примеров. Я собираюсь объяснить, какой путь выбирает JavaScript, чтобы решить, что выводить.
Пример:
[] == 0
Запуск проверки алгоритма:
а. Отличаются ли типы операндов? да, значит, нам предстоит пройти долгий путь:
б. Сравниваем ли мы null с undefined? нет, перейдите к следующей проверке:
c. Сравниваем ли мы число со строкой? нет, перейти к следующей проверке:
d. Сравниваем ли мы логическое значение с чем-то другим? нет, переходите к следующей проверке:
е. Сравниваем ли мы объект с числом, строкой или символом? ДА! мы сравниваем объект массива с числом. Хорошо, давайте приведем массив к примитиву и снова вызовем double equals:
- вызов
valueOf
в массиве: вывод[]
, что не является примитивом, и мы не можем его использовать. ПозвонитеtoString
: - вызов
toString
в массиве: вывод“”
. Прекрасно, это примитивная строка, и мы можем ее использовать.
Вызов double равняется снова с результатом принуждения объекта к примитиву:
“” == 0
А теперь вернемся к началу и начнем сначала:
а. Одинаковы ли типы? нет, выбирай долгий путь:
б. Сравниваем ли мы null с undefined? нет, перейдите к следующей проверке:
c. Сравниваем ли мы число со строкой? ДА, теперь давайте преобразуем строку в число. приведение пустой строки к числу вернет 0. Вызовите double равняется снова и начните заново:
0 == 0
а. Одинаковы ли типы? Да, вызов тройки равен 0 === 0
, и результат будет верно
Итак, в итоге:
[] == 0 ↓ "" == 0 ↓ 0 == 0 ↓ 0 === 0 → true
Запомним этот пример и рассмотрим еще несколько примеров.
Пример:
[] == “0”
[] == "0" ↓ "" == "0" ↓ "" === "0" → false
Пример:
false == 1
false == 1 ↓ 0 == 1 ↓ 0 === 1 → false
Пример:
{} == false
{} == false ↓ {} == 0 ↓ "[object Object]" == 0 ↓ NaN == 0 ↓ NaN === 0 → false
Пример:
undefined == false
undefined == false ↓ undefined == 0 → false
Пример:
[1,2,3] == 123
[1,2,3] == 123 ↓ "1,2,3" == 123 ↓ NaN == 123 ↓ NaN === 123 → false
Пример:
[123] == 123
[123] == 123 ↓ "123" == 123 ↓ 123 == 123 ↓ 123 === 123 → true
Пример:
[123] == “123”
[123] == "123" ↓ "123" == "123" ↓ "123" === "123" → true
В следующих примерах x определяется следующим образом:
var x = { a: 'a', toString: function () { return false; }, valueOf: function () { return new Boolean(true); } };
Пример:
x == “0”
x == "0" ↓ false == "0" ↓ 0 == "0" ↓ 0 == 0 ↓ 0 === 0 → true
Пример:
x == 5
x == 5 ↓ false == 5 ↓ 0 == 5 ↓ 0 === 5 → false
Пример:
x == [1,2,3]
x == [1,2,3] // both are objects ↓ x === [1,2,3] // they are stored at different addresses → false // the address is not the same
Заключение
Даже если на практике вы можете не использовать двойное равенство, знание того, как работает приведение в JavaScript, действительно важно. Принуждение происходит повсеместно в JavaScript. Знание внутренних абстрактных операторов и алгоритмов принуждения может помочь вам написать лучший код JavaScript и избежать ненужных ошибок.
Надеюсь, эта статья оказалась для вас полезной. Обязательно ознакомьтесь с другими моими карманными ссылками на Medium: Функции JavaScript, Прототипы JavaScript и Асинхронный JavaScript.