Прочитав эту статью, вы будете знать все, что вам нужно знать о принуждении и операторе двойного равенства в 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.