Об Equality Algorithms
- вторник, 18 февраля 2025 г. в 00:00:07
Задумывался ли ты когда-нибудь о том, как работает сравнение в javascript под капотом? Я - нет. Но в один прекрасный день, проходя чеклисте по JS на roadmap.sh, я наткнулась на пункт Equality Algorithms, помеченную как Advance. Благодаря этому, я решила разобраться в данной теме получше и написать небольшую заметку. Здесь не будет подробного разбора, а лишь краткие выжимки, блок‑схемы и обобщения, которые я вынесла для себя, изучая спецификации и прочие ресурсы (список я оставлю в конце).
Вероятно, вы знаете, что в JS есть три способа сравнения значений: ==
, ===
и Object.is()
. Напомню, что оператор ==
работает с неявным приведением к типу (например, сравнение значений 1 == '1'
выдаст true
), а ===
сравнивает значения без приведения к типу (аналогичный пример выдаст false
). Object.is()
работает практически также, как и ===
, за исключением случаев с NaN
, -0
и +0
.
Эти три операции под капотом используют следующие алгоритмы сравнения:
isLooselyEqual ==
isStrictlyEqual ===
SameValue Object.is()
Каждый из алгоритмов работает по разному, поэтому стоит рассмотреть каждый чуть подробнее. Но для начала нужно взглянуть на алгоритм SameType.
Принимает два аргумента и просто сравнивает их типы и возвращает true или false. Эта операция используется во всех алгоритмах и от его результата зависят дальнейшие шаги.
Используя ==
в своем коде, мы также используем алгоритм сравнения isLooselyEqual
. Если коротко, то алгоритм содержит в себе много условий и он не то чтобы сложен сам по себе.
Бездумное использование этого типа сравнения и стрельба по своим собственным коленям объединяет одно: в определенный момент становится больно. Например, в спецификации прописано, что если один операнд null
, а второй undefined
— то алгоритм вернет true
(и наоборот). Но если второй операнд другого типа (например, boolean
) — вернет false
. И я вижу много потенциальных случаев, где можно было бы допустить ошибку в своем коде — например, где кто‑то сравнивает переменную undefined
c false
, наивно полагаясь на преобразование типов.
Что нужно знать:
Если тип SameType
вернет true
, то выполняется алгоритм isStrictlyEqual(x, y)
(о нем чуть позже);
Если сравниваются типы number
и string
, то тип string
будет приведен к типу number
;
Объект при сравнении с другими типами преобразуется в примитив, вероятнее всего у объекта будут вызван метод valueOf
(подробнее цепочку проследить можно в спецификации);
Если один из операндов типа boolean
, то сначала он будет преобразован в тип number
, а после это будет выполнено сравнение операндов по алгоритму;
NaN
не равен NaN
, +0
равен -0
.
Я здесь не стала подробно расписывать все кейсы (это есть в спецификации), но это, на мой взгляд, основное. Честно говоря, я не могу придумать пример, где этот способ сравнения был бы полезен — разве что в твоем проекте не определились с типом поля ID
- вроде number
, но BE присылает string
, а времени на рефакторинг категорически не хватает и очень лениво заниматься преобразованием каждый раз самому. Но я бы рекомендовала бы использовать все же строгое сравнение, так что айда смотреть на то, как работает алгоритм isStrictlyEqual
Но перед этим хочу обратить внимание на алгоритм SameValueNonNumber
— как раз таки он лежит в основе следующих алгоритмов.
По алгоритму мы сравниваем два НЕ числовых значения и устанавливаем, что их типы одинаковы. Если два значения равны — вернет true
, иначе false
. Объекты сравнивают по ссылке (равны, если оба ссылаются на одно и то же), строки по количеству и порядку символов и т. д.
Этот алгоритм используется в строгом сравнении ===
. На мой взгляд, он очень простой: значение может быть равно только самому себе — все! Нет никаких скрытых преобразований, что исключает неприятные сюрпризы.
Опять же, если не сильно вдаваться в подробности спецификации на 10 строк, алгоритм работает следующим образом:
Если типы не совпадают — вернуть false
Если в сравнении участвуют тип number
— то возвращаем результат сравнения двух чисел (включая такие моменты, как NaN
не NaN
и +0
равно -0
)
Для всего остального есть SameValueNonNumber
😎
Из интересного, в спецификации упоминается, что под капотом языка этот алгоритм используется в конструкции switch/case
и в поиске элемента в массиве по индексу (indexOf
, lastIndexOf
). Поиск по индексу в массиве любопытный момент, но я придумала не самый практичный кейс: вряд ли кто‑то будет искать в массиве NaN
, например 🙃
Дальше рассмотрим алгоритм, который отличается от isStrictlyEqual
только тем, что для сравнения number
используется немного другая операция, по результату которой, помимо прочего, +0
не равен -0,
а NaN
равен NaN
.
Под капотом этот алгоритм используется не только при вызове метода Object.is()
, а также участвует в работе оператора instanceof
, в Object
и Proxy
, WeakSet
и WeakMap
и далее по длиннющему списку упоминаний в спецификации — всего их 51. Из этого можно сделать вывод, что, работая с объектами, мы используем этот алгоритм сравнения чаще всего, даже не замечая этого.
Про этот алгоритм хочется упомянуть, так как он также является одним из основных алгоритмов сравнения. Он работает практически также, как SameValue
и isStrictlyEqual
, но с разницей в способе сравнения number
. Здесь уже при сравнении переменных со значениями +0
и -0
вернется true
, также как и при сравнении двух значений NaN
. Он не настолько распространен по спецификации, как SameValue
и под капотом используется в массивах (например, метод includes
),Map
и Set
.
Это был интересный опыт изучения спецификации ES. Если говорить о самих алгоритмах — то они помогают глубже нырнуть в принципы работы языка в целом, приносят больше понимания в работу некоторых структур. Теперь я могу спокойно использовать NaN
в качестве ключа в Map
и не боятся, что запись где‑то затеряется 😃 А если серьезно, то в моей обычной практике это не настолько востребованная тема, какой она могла бы быть (обычно, я ограничиваюсь строгим сравнением в случае примитивов). Но это же опыт, никогда не знаешь, когда тебе смогут пригодиться эти знания на практике и эти знания намного ценнее, чем простое отличие ==
и ===
.
Список ресурсов:
Which JavaScript equality algorithm should you use? — в общих чертах про алгоритмы сравнения;
JavaScript Equality Table — таблица сравнения значений для каждого из способов;
Спецификации алгоритмов isLooselyEqual, isStrictlyEqual, sameValue и sameValueZero.