Многие разработчики постоянно используют === вместо ==, но почему?

Большинство из просмотренных мной обучающих программ предполагают, что поскольку слишком сложно предугадать приведение типов в JavaScript, то лучше всегда использовать ===.

Есть несколько обучалок, подкрепляющих ошибочную информацию и мифы в интернете. Помимо этого, многие правила линтеров и популярных сайтов уверенно настроены на постоянное использование ===.

Всё это ведёт к тому, что множество программистов вместо того, чтобы расширять своё понимание, исключают часть языка, расценивая её как дефект. Вот два примера случаев, в которых лучше использовать ==.

1. Проверка на пустые значения

if (x == null)

vs

if (x === undefined || x === null)

2. Прочтение вводимых пользователем данных

let userInput = document.getElementById('amount');
let amount = 999;
if (amount == userInput)
vs
if (amount === Number(userInput))

В этой статье мы глубоко погрузимся в тему: разберем различия, поймем приведение, рассмотрим некоторые популярные случаи применения операторов и в завершение сформируем рекомендации для принятия собственного решения.

Введение

В JavaScript равенство определяется двумя операторами.

  1. === — Строгое равенство (тройное).
  2. ==— Абстрактное равенство (двойное).

Раньше я всегда использовал ===, поскольку мне сказали, что это лучше и предпочтительнее, чем ==, добавив при этом, что думать тут не о чем. Будучи ленивым человеком, я посчитал это удобным и не стал заморачиваться.

Так продолжалось, пока я не посмотрел “Deep JavaScript Foundations” на Frontend Masters от Кайла Симпсона— автора серии книг “Вы не знаете JS”.

Тот факт, что я, будучи профессиональным программистом, всерьёз не задумывался об одном из операторов, который использую ежедневно, сподвиг меня на просвещение и мотивацию людей учиться лучше понимать код, который мы пишем.

Где он  —  исток истины?

Важно знать, в чём кроется истина. Она не в Mozilla, W3schools и не в сотнях статей, гласящих, что === лучше, чем ==, и точно не в этой. Она в спецификациях JavaScript, где вы можете найти документацию по работе этого языка. Далее ссылка на англоязычную спецификацию JS: tc39.

Развеиваем мифы

1. == проверяет только значения (упрощённая проверка)

Если мы заглянем в спецификацию, то из определения станет понятно, что первое действие алгоритма — это проверка типа.

Если Type(x) совпадает с Type(y), тогда a. Вернуть результат выполнения строгой проверки равенства x===y.

2. === Проверяет типы и значения (строгая)

Здесь спецификация нам также показывает, что происходит проверка типов и, если они различны, значение вообще не проверяется.

Если Type(x) отличен от Type (y), вернуть false.

Реальное отличие двойного равенства от тройного в том, допускаем ли мы приведение.

Приведение в JavaScript

Приведение или преобразование типов — это одна из фундаментальных составляющих любого языка программирования. И оно ещё более важно для динамически типизированных языков вроде JavaScript, в которых компилятор не станет на вас ругаться, если типы вдруг изменятся.

Понимание приведения означает, что мы можем интерпретировать код так же, как это делает JavaScript, что даёт больше возможностей для расширения и минимизирует ошибки.

Явное приведение

Приведение может стать явным, когда программист вызывает один из этих методов и тем самым меняет переменную:

Boolean(), Number(), BigInt(), String(), Object()

Пример:

let x = 'foo';
typeof x // string
x = Boolean('foo')
typeof x // boolean

Неявное приведение

В JavaScript переменные слабо типизированы. Это означает, что они могут быть автоматически преобразованы (неявное приведение). Обычно такие случаи связаны с арифметическими операциями +/-*, окружающим контекстом или использованием ==.

2 / '3' // '3' приведена к 3
new Date() + 1 //  приведена к строке даты, заканчивающейся на 1
if(x) // x приведена к boolean
1 == true // true приведено к числу 1
1 == 'true' // 'true' приведено к NaN
`this ${variable} будет приведено к строке

Неявное приведение — это палка о двух концах. При аккуратном использовании, оно может повысить читаемость кода и сделать код короче. При ошибочном понимании оно становится формулой разочарования и причиной, по которой люди могут негативно отзываться о JavaScript.

Алгоритмы равенства в Nutshell

Абстрактная проверка равенства ==

  1. Если X и Y одного типа — выполнить ===.
  2. Если X —null, а Y — undefined или наоборот, тогда — true.
  3. Если одно является числом, то второе также привести к числу.
  4. Если одно является объектом, то привести к примитиву.
  5. Вернуть false.

Строгая проверка равенства ===

  1. Если типы не совпадают — false.
  2. Если типы совпадают — сравнить значения и вернуть false для NaN.
  3. -0 — true.

Популярные случаи применения

1. Одинаковый тип (большинство случаев)

Если типы одинаковы, то === эквивалентно ==. Следовательно, лучше использовать более семантический вариант.

1 == 1 // true                ......        1 === 1 // true'foo' == 
'foo' // true        ......       'foo' === 'foo' //true

“Я предпочитаю===, только когда типы различны.”

Это нелогичный аргумент, скорее он напоминает двойное нажатие на save или пятикратное обновление. Мы же не вызываем метод дважды в коде просто на всякий случай, не правда ли?

2. Различные типы (примитивы)

В первую очередь я хочу обратить ваше внимание на то, что к различным типам не относятся типы, которые вы не знаете. Незнание типов говорит о том, что у вас в коде есть куда большая проблема, чем вопрос использования === или ==. Знание же типов говорит о более глубоком понимании кода, что в итоге ведёт к меньшему числу ошибок и более надёжному ПО. 

Когда у нас есть несколько возможных типов, понимание приведения позволит решить, использовать его или нет, что определит выбор в пользу === или ==. К примеру, у нас есть число или строка. 

Вспомните, что алгоритм предпочитает числа, следовательно он постарается использовать toNumber().

let foo = 2;
let bar = 32; // число или строка
foo == bar // если bar строка, то она будет приведена к числу
foo === Number(bar) // делает в основном то же самое
foo === bar // будет всегда проваливаться, если bar окажется строкой

3. null и undefined

null и undefined равны друг другу при использовании ==.

let foo = null
let bar = undefined;

foo == bar // true

foo === bar // false

4. Непримитивы: объекты, массивы

Сравнение непримитивов вроде объектов и массивов не должно производиться с помощью == или ===.

Рекомендации для принятия решения

  1. Предпочитайте == во всех случаях, где он может быть использован.
  2. == при известных типах — по желанию, когда хотите преобразовать типы.
  3. Лучше знать типы, чем не знать.
  4. Не используйте ==, если не знаете типы.
  5. Если типы вам известны, то == — это эквивалент ===.
  6. === бесполезен, когда типы не совпадают.
  7. === необязателен, когда типы совпадают.

Причины избегать ==

Есть некоторые случаи, когда не стоит использовать ==, не имея реального понимания false в JavaScript:

== c 0 или "" или "   "
== с непримитивами 
== true или == false

Заключение

Что касается моего опыта, то я всегда знаю тип переменной, с которой имею дело, а если не знаю, то использую typeof, чтобы допустить лишь те типы, которые ожидаю.

Ещё несколько рекомендаций для тех, кто дочитал до конца:

  1. Если вы не можете или не будете знать типы, тогда использование === — это единственный разумный выбор.
  2. Незнание типов говорит о том, что вы не особенно хорошо понимаете код, поэтому попробуйте сделать рефакторинг.
  3. Знание типов ведёт к созданию лучшего кода.
  4. Если типы известны, то == предпочтительней, в противном случае используйте ===.

Спасибо за чтение, я надеюсь, что эта статья помогла вам углубить свои познания в JavaScript. 

Читайте также:


Перевод статьи Seifeldin Mahjoub: Stop Using === Everywhere

Предыдущая статьяПонятие о миграциях в TypeORM
Следующая статьяУдалённые вызовы процедур в Golang