Больше подробностей, чем вам нужно или нужно. Также головоломки!

Актуальные вещи на JavaScript ниже сгиба

Я чертовски люблю равенство. Мне нравится звук, который он издает, когда шлепаешь его по столу. Мне нравится его запах по утрам и ощущения вечером. Я люблю говорить об этом, я люблю спорить об этом и, больше всего, я люблю отчаянно надеяться на это. Любой, кто когда-либо имел честь, удачу и привилегию быть свидетелем одного из моих многочисленных феминистских разглагольствований из первых рук, знает, как долго я готов обсуждать это.

Поэтому мне очень повезло, что я родился в такое благословенное прогрессивное время, когда этот инстинкт не только питается, но и питается окружающим меня миром, миром, в котором каждый образ из каждой точки является напоминанием о справедливость, с которой мы все попали в этот мир, мир, в котором каждый из нас делает свой первый шаг на равных. Например, вот несколько белых парней, отказывающихся от права женщин распоряжаться своим телом. Глобально. Кто твой любимый чувак? Дай мне знать в комментариях!

Равенство, числа и принуждение

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

Если вы не уверены ни в одном из них, попробуйте сделать разумное предположение, рассуждая (а не спекулируя) на основе того, что вы знаете. Это такой же впечатляющий навык, как просто знание кучи дерьма. Предполагается некоторое понимание различия между абстрактным (иногда известным как расплывчатое) равенство и строгое равенство в JavaScript (т. е. двойное и тройное равенство), а также понимание разницы между примитивами (строка, число и т. д.) и объектами.

Загадки

1 В JavaScript существует бесконечное количество истинных значений, которые не равны истине, как и в JavaScript бесконечное количество истинных значений (т.

Однако сколько истинных значений вы можете придумать для x, которые также (абстрактно/двойно) равны false? Важная подсказка: что общего у них почти у всех?

e.g.:

x == false; // true
x ? true : false; // true

2 В каком случае результат прибавления единицы к числу может быть равен результату прибавления двух так, чтобы получилось следующее?

typeof x; // "number"
x + 1 === x + 2; // true

И наоборот, когда может быть наоборот?

typeof y; // "number"
y - 1 === y - 2; // true

3 Какое значение для x никогда не будет равно самому себе, чтобы получить следующее?

x !== x; // true
x != x; // true

4 И наоборот, какое значение для x имеет абстрактное равенство с не самим собой, такое, что верно следующее?

x == !x; // true;

5 Существуют ли какие-либо значения для x типа number, которые могут привести к следующему выводу?

typeof x; // "number"
3 * x - x === 2 * x;  // false

Мол, очевидно, есть, иначе я бы не спрашивал, верно?

А ассоциативность? Это всегда так? Можете ли вы придумать значения для x, y и z, которые дадут следующее?

(x + y) + z === x + (y + z); // false

Примечание: это не проблема BODMAS ни для одного из вопросов.

6 Какие значения для x и y дадут следующее? Бонусный балл: каким должен быть z?

x.isNaN(z); // true
y.isNaN(z); // false

Примечание: ответ заключается не в том, чтобы просто создать пару разных объектов x и y с методами isNaN (хотя это сработает).

7Бонусный раунд! ES6 представляет Object.is, который проверяет, имеют ли две переменные одно и то же значение. Значение из вопроса 3, которое не равно самому себе, тем не менее, содержит то же значение, что и оно само, и, следовательно, возвращает здесь true. Это:

x !== x; // true
Object.is(x, x); // true

Знаете ли вы два различных значения, которые считаются строго равными? Это:

x === y; // true
Object.is(x, y); // false

Ответы

Я собираюсь дать фактический ответ на каждый вопрос с TL; DR почему. Я добавлю более сложный раздел в конце для тех, кто ищет немного больше деталей, но, пожалуйста, добавьте комментарии, если вы думаете, что видите какие-либо ошибки или думаете об ответах, которые я пропустил.

На этом этапе я рекомендую прочитать прекрасную статью MDN Сравнения равенства и одинаковость, которая доступна, достаточно всеобъемлюща и содержит отличную таблицу правил абстрактного равенства, а затем пересмотреть свои ответы. Я также рекомендую просто прочитать спецификацию по абстрактному равенству (ES5.1, ES6, latest), которая, естественно, полностью исчерпывающая (хотя и немного суховатая) и очень короткая.

Как правило, абстрактное равенство в основном проверяет, являются ли два компаратора объектами, и если да, возвращает, являются ли они одним и тем же объектом (а не только тем, что они содержат одни и те же свойства и значения); затем он проверяет, является ли первый компаратор undefined или null, и возвращает значение второго компаратора; наконец, он преобразует практически все остальное в числа и сравнивает два числа.

1. Истинные ценности, равные ложным

Вот некоторые истинные значения, которые имеют абстрактное равенство с false:

[] == false; // true
[""] == false; // true
[0] == false; // true
["0"] == false; // true
"0" == false; // true
" " == false; // true
"\n\t" == false; // true

WTF/TL;DR: при сравнении любого массива с false JavaScript видит, что у него есть объект (массив) и примитив (логическое значение), поэтому сначала он преобразует массив в примитив ( пустая строка в первых двух случаях и “0” в следующих двух). На данный момент у нас есть все строки. При сравнении любого нелогического примитива с логическим значением JavaScript приводит обе стороны к числу. false и “0”, очевидно, становятся 0, но то же самое происходит и с пустой строкой, да и с любой строкой, содержащей только пробелы.

Дополнительный ответ:

new Boolean(x) == false; // true where x is any falsy
new Boolean(x) ? true : false // always true

WTF/TL;DR: JavaScript имеет набор соответствующих объектов для каждого примитива с функциями, которые можно использовать в качестве конструкторов, вызывая их с помощью ключевого слова new. Обычно они используются за кулисами для плавного обертывания примитивов в объекты, чтобы вы могли использовать для них свойства (например, length, такие методы, как toString и т. д.). При использовании в качестве конструкторов они приводят свои аргументы к правильному примитиву (здесь — логическому) и возвращают объект, который хранит этот примитив и возвращает его, если сам объект принудительно преобразуется в примитив. Следовательно, любой ложный аргумент приведет к объекту, абстрактно равному false; однако, будучи объектом, он и сам всегда будет правдив (истинность объектов в ES6 spec).

2. Чертовы огромные цифры

Два числа устанавливают ограничение на надежную арифметику в JavaScript:

Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; //true
Number.MIN_SAFE_INTEGER - 1 === Number.MIN_SAFE_INTEGER - 2; // true

WTF/TL;DR:любое число, равное (или превышающее) максимальное целое число или меньшее или равное минимальному целому числу, может возвращать необычные (и математически неверные) результаты при использовании с арифметическими операциями. Спецификация ES6 очень четко говорит об этом: [максимальное целое число] наибольшее целое число n, такое что n и n + 1 оба точно представляются как числовое значение. Это наиболее ясно с самим максимальным безопасным целым числом, которое приведет к тому же числу при увеличении на единицу или два. Это связано с тем, что JavaScript реализует все числа* как числа с плавающей запятой IEEE 754–2008, которые хранят числа в виде дробей, состоящих из степеней двойки.

*Числа временно преобразуются в 32-битные целые числа с помощью побитовых операторов.

3. Поход в NaN на Рождество

Это верно только для одного значения и, я думаю, уникально во всей компьютерной науке (не думайте, что это математически правильно):

NaN !== NaN; // true

WTF/TL;DR: для этого есть всевозможные предполагаемые причины, наиболее убедительная из которых (ИМО) заключается в том, что вычисления с NaN никогда не приводят к другому числу (например, NaN/NaN должно оцениваться как NaN) .

Следовательно, x !== x считается быстрой и надежной проверкой NaN.

4. Любовь во времена принуждения

Мне нравятся все детали этого примера, но просто чтобы получить ответ:

[] == ![]; // true

WTF/TL;DR: Короче говоря, сначала происходит отрицание. Это приводит пустой массив к логическому значению, а затем отрицает его (к false), поэтому обе стороны в конечном итоге приводятся к числу: 0. Ага. Я расскажу об этом полностью в конце. Шагов множество. Это так здорово/смехотворно 😃

5. Работа с упрямыми поплавками

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

3 * 0.1 - 0.1 === 2 * 0.1; // false
(0.1 + 0.2) + 0.3 === 0.1 + (0.2 + 0.3); // false

WTF/TL;DR: Это еще одно следствие использования чисел в JavaScript (среди многих других языков). Хотя возможность представить десятую часть чего-то может показаться довольно простым требованием к программам, довольно сложно точно построить его кратное из степеней двойки. По этой причине, я предполагаю, что наиболее распространенные примеры, на которые жалуются, включают 0.1 &0.3, поскольку последнее является первым кратным первого, что дает удивительные результаты. Это та же проблема, что и: 0.1 + 0.2 === 0.3; // false.

6. NaNcy Drew взломала еще один

z = anyValueThatCannotBeCoercedToAValidNumber;
window.isNaN(z); // true
Number.isNaN(z); // false

WTF/TL;DR:Долгое время JavaScript содержал одну глобальную функцию isNaN, которая на первом этапе преобразует вводимые данные в число. Следовательно, все, что не может быть приведено к действительному числу ({}, ‘bunghole’ и т. д.), будет приведено к NaN, а не проверено, чтобы убедиться, что это NaN.

В ES6+ мы также получаем доступ к Number.isNaN, который на первом этапе проверяет, является ли его ввод числом, и, если нет, возвращает false. Как правило, это намного полезнее, но старая глобальная переменная была сохранена, чтобы не нарушать старый код, который полагался на это довольно ошеломляющее поведение.

7. Object.is или Object.notToIs

Теперь вы уже должны знать, что один из них NaN, так что просто чтобы удовлетворить ваше любопытство по поводу другого:

Object.is(NaN, NaN); // true
NaN === NaN; // false
Object.is(-0, 0); // false
-0 === 0; // true

WTF/TL;DR: Коротко о том, что SameValue (представленное в ES5) — это то же самое, что и абстрактное равенство с немного другими исключениями, и до недавнего времени они были написаны именно так. Я добавлю еще несколько рассуждений о том, как ES8 на самом деле зависит от SameValue при оценке равенства в конце, но все, что вам в основном нужно знать, это то, что алгоритм строгого равенства (который также используется для определения абстрактного равенства между переменными одного и того же типа) имеет некоторые специальные, жестко запрограммированные угловые случаи, как указано выше, и что Object.is имеет такие же специальные, жестко запрограммированные угловые случаи с противоположным результатом.

Дополнительный кредит!

Это все для ответов. Дайте мне знать в комментариях, если вы подумали о других примерах или я что-то пропустил/ошибся. А пока вот настоящее погружение в некоторые из мельчайших деталей из вышеперечисленного.

В этом разделе я пошагово пройдусь по некоторым алгоритмам, описанным в спецификации ECMAScript. Однако важно отметить, что маловероятно, что ваш движок шаг за шагом шаг за шагом проходит через спецификацию; скорее, он почти наверняка делает что-то существенно более эффективное, результат которого идентичен выходу алгоритмов спецификации (высоко декларативных). Цель спецификации — описать поведение языка. Я считаю, что нам, разработчикам, полезно шаг за шагом пройтись по заявленному поведению, чтобы понять, как устроен язык и как описывается его поведение.

Странное принуждение к 0

Ряд приведенных выше примеров основан на некоторых загадочных функциях JavaScript, в частности:

  1. «Абстрактная» операция ToPrimitive;
  2. рекурсивный характер абстрактного равенства, который может привести к двойному принуждению;
  3. Необычные результаты приведения некоторых строк к числу.

Абстрактные операции используются спецификацией для уточнения и объяснения семантики языка. Это именованные и параметризованные соглашения об алгоритмах, которые можно повторно использовать в спецификации для объяснения поведения языка. По сути, они являются функциями, как я понимаю, в истинном математическом смысле, который заключается в том, что они просто декларируют набор решений, относящихся исключительно к вводу и выводу. Это отличается от того, что мы часто считаем функциями, которые, скорее всего, являются императивными и, по сути, процедурами. Они «абстрактны», потому что они не являются императивными и не связаны с реализацией — любая реализация JavaScript может реализовать абстрактные операции по-другому или вообще не реализовывать, пока соблюдается общая семантика языка, описанная абстрактными операциями. Абстрактные операции распознаются в спецификации как имена в верхнем верблюжьем регистре, за которыми следуют круглые скобки, содержащие параметры.

ToPrimitive – это абстрактная операция, преобразующая объекты в примитивы (спецификация ES6). Как вы можете видеть из спецификации, это немного многословно (и довольно запутанно). В случае любого объекта (включая массивы) необходимо выполнить примерно следующие шаги:

  1. Проверьте, является ли ввод примитивным, и если да, верните его;
  2. Проверьте, есть ли во входных данных хорошо известный символ @@toPrimitive в качестве свойства (символы намного выходят за рамки этого блога, но я хотел бы рассказать о них в какой-то момент, потому что они супер - захватывающая функция), вызовите ее, если это так, и верните вывод, если это примитив, или выдайте ошибку, если нет;
  3. По умолчанию сначала выполняется попытка приведения числа;
  4. Вызовите метод valueOf объекта и верните его вывод, если это примитив;
  5. Разочарование — на выходе один и тот же дерьмовый объект;
  6. Вызовите метод toString объекта и верните его вывод, если это примитив;
  7. Скиньте ошибку.

К счастью, на шаге 6 в большинстве случаев мы получим примитив, но это довольно сложное путешествие. Объекты, как правило, возвращают “[object Object]” на шаге 6 (хотя теперь вы можете переопределить это), но массивы переопределяют это, просто вызывая метод join и возвращая его результаты (спецификация ES6). Следовательно, когда функция внутреннего языка запрашивает примитив (например, абстрактное сравнение на равенство), объекты в основном возвращают непустую строку, которая возвращает NaN при приведении к числу, а массивы возвращают пустую строку или разделенный запятыми список своих рекурсивно toString()ed. содержание. Это приводит к следующему поведению:

{} == '[object Object]'; // true
[] == ''; // true
[[]] == ''; // true, etc.
[0] == '0'; // true
[[0]] == '0'; // true, etc.

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

[] == false;

ToPrimitive будет вызываться под капотом и приводить пустой массив к примитиву (пустой строке). Теперь у нас есть:

'' == false;

Это может показаться ошибкой — это не одни и те же типы — но спецификация абстрактного сравнения на равенство очень ясно говорит об этом: просто сделайте это еще раз! Правильно, шаги 10 и 11 алгоритма абстрактного равенства являются рекурсивными и указывают, что если только одна сторона сравнения является объектом, сначала привести его к примитиву, а затем вернуть результат сравнения абстрактного равенства между результирующим примитивом и Другая сторона.

Эта рекурсия, конечно, приводит к двойному принуждению. В этом примере, поскольку абстрактное равенство предпочитает числа, пустая строка и логическое значение приводятся к числу. На самом деле это занимает два шага (как могли заметить зоркие читатели алгоритма сравнения строгого равенства). Во-первых (шаг 9 алгоритма — разные типы с логическим значением в правой части), правое значение приводится к числу, и результат рекурсивно вызывается абстрактным сравнением на равенство:

'' == 0;

Наконец (шаг 7 алгоритма — левое значение — это строка, а правое — число), правое значение приводится к числу, и снова рекурсивно вызывается абстрактная операция равенства. результат:

0 == 0;

На этом этапе абстрактное сравнение на равенство зависит от строгого сравнения на равенство и вызывает:

0 === 0;

Это правда, что это ложь! Но почему? Поскольку пустая строка была приведена к 0. Но почему? Что ж, это подводит нас к нашей третьей и последней квир-интермедии (как я люблю заканчивать все свои вечера):

Странное приведение строки к 0. Это последний искажающий фактор, из-за которого некоторые истинные значения становятся равными ложным. Мы закончили с абстрактным сравнением на равенство между пустой строкой и false, понимая, что они оба будут приведены к числам, чтобы дать нам окончательный результат нашего сравнения. Теперь должно быть довольно интуитивно понятно, что false и ‘0’ приводят к 0, но вы можете быть удивлены, узнав, что пустая строка и любые строки, содержащие все пробелы, делают то же самое.

Это своеобразная, но вполне преднамеренная особенность JavaScript. Спецификация преобразования строк в числа описывает грамматику для интерпретации того, что она называет StringNumericLiteral. Литерал — это, конечно, способ обозначения фиксированного значения, например:

a = []; // a initialised with an array literal
b = 42; // b initialised with a numeric literal

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

Возможно, я наивен, но мне такое поведение кажется необычным.

Логические объекты всегда правдивы

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

Как я уже говорил, у примитивов нет свойств. Только объекты имеют свойства. Следовательно, некоторые синтаксические удобства (например, получение length строки) требуют функции объектно-ориентированных языков, называемой упаковкой или обтеканием. Это когда примитивный тип значения заключен в объект, чтобы включить или облегчить объектно-ориентированное поведение, которое в данном случае представляет собой доступ к свойствам или вызов методов.

Каждый примитив JavaScript имеет соответствующий объект, который наследуется от прототипа Object и включает собственную версию некоторых методов Object.prototype, таких как полезный метод toString.

Итак, когда вы звоните toString по номеру, под капотом происходит следующее:

  1. Ваш номер x "упакован" при звонке new Number(x);
  2. Вы вызываете метод toString этого блока — Number.prototype имеет свой собственный (бесполезный) метод toString, поэтому он не поднимается по прототипной цепочке наследования до Object.prototype;
  3. Как только этот метод возвращает вашу прекрасную коробку, она отбрасывается и отправляется на сборку мусора.

Эти ящики с объектами-примитивами имеют внутренний слот (т. е. специальное, секретное свойство — один для другого блога), который содержит примитив соответствующего типа этого объекта, полученный из его ввода. Например, new Boolean(‘0’) будет содержать примитив false, а new Number(‘bunghole’) будет содержать примитив NaN.

Каждая из этих оберток (хорошо известные встроенные объекты) демонстрирует какое-то увлекательное или причудливое поведение. Например:

x = new Number('anal fisting');
Number.isNaN(x); // true
x === x; // true
x.valueOf() !== x.valueOf(); // true
y = new Boolean(false);
y == false; // true
y ? true : false; // true

В первом примере объект, представляющий NaN, будучи объектом, равен самому себе, но содержит значение, не равное его значению. Это, очевидно, просто зависание равенства NaN, но я думаю, что интересно увидеть фактическое представление NaN как Number, которое равно самому себе.

Во втором примере мы получаем истинное значение fun-fun, равное false, которое вдохновило этот раздел.

Примечательно, что в обоих случаях при использовании абстрактного равенства известные встроенные объекты никоим образом не переопределяют поведение ToPrimitive. Вместо этого у них есть собственные valueOf в соответствующих прототипах, которые возвращают содержимое их внутреннего слота (то есть значение, которое они представляют). Следовательно, при использовании в абстрактном равенстве (например, new Boolean(false) == false;) ToPrimitive возвращает примитив на шаге 4 и никогда не видит разочарования шага 5. Примитив в этом случае имеет тот же тип, поэтому при рекурсивном абстрактном сравнении равенства , двойное приведение не требуется, и возвращается строгое равенство двух (false === false).

Это много.

Для тех, кто действительно хочет спуститься и испачкаться, вот соответствующие части спецификации:

  1. Обёртка объекта с помощью ToObject;
  2. Получение свойств с помощью GetV (который вызывает ToObject);
  3. Получить методы с помощью GetMethod (который вызывает GetV).

Массив равен не массиву!

Я обещал подробности этого необычного поведения, и, черт возьми, вы получите чертову подробность! Это одна из моих любимых дерьмовых особенностей JavaScript:

[] == ![];

Что за херня на роликах здесь происходит? Те, кто внимательно прочитал вышеизложенное — во-первых, получите жизнь! - надо уметь собирать. Почему бы не попробовать сначала разобраться и посмотреть, есть ли у вас деньги?

В любом случае, вот пошаговое пошаговое руководство, основанное на абстрактном сравнении равенства ES6:

  1. Отрицание происходит первым, поскольку выражения должны быть оценены до их сравнения. Чтобы инвертировать массив, он приводится к логическому значению (true — все объекты правдивы), а затем инвертируется.
  2. Теперь у нас есть: [] == false;.
  3. Абстрактное сравнение на равенство видит объект, сравниваемый с примитивом, и сходит с ума. Он приводит пустой массив к примитиву, который, как вы теперь знаете, фактически вызывает toString, который фактически вызывает join.
  4. Теперь у нас есть: ‘’ == false;.
  5. Абстрактное сравнение на равенство видит строку и логическое значение, поэтому логическое значение приводится к числу.
  6. Теперь у нас есть: ‘’ == 0;.
  7. Абстрактное сравнение на равенство видит строку и число, поэтому строка приводится к числу.
  8. Теперь у нас есть: 0 == 0;. Просто не надо.
  9. Наконец, абстрактное сравнение на равенство видит два примитива одного типа и возвращает строгое сравнение на равенство: 0 === 0;.

Object.is и SameValue

Хотя мой синопсис выше определенно не был неверным — ES5 описывает алгоритм SameValue, а ES6 делает его доступным для программиста через Object.is — он упускает один интересный (хотя и неважный) нюанс. Вот полная (э) история:

ES5 описывает SameValue. Программист JavaScript не имеет прямого доступа к нему, но он используется внутри языка для проверки определенных действий. Он появляется во внутреннем методе [[DefineOwnProperty]] для обеспечения разрешений недоступных для записи и ненастраиваемых свойств.

ES6 предоставляет эту внутреннюю абстрактную операцию через Object.is. Он также использует его во всей спецификации. Например, в таких терминах описывается гораздо больше поведения объектов, включая геттеры и сеттеры, а также цепочки прототипов (или путь наследования объектов7.3.19) , итерации, связанные функции и т. д. В основном сейчас многое описывается в SameValue терминах. Также вводится очень похожая абстрактная операция — SameValueZero, которая, за исключением случая нулей (она рассматривает -0 и 0 как идентичные), возвращает те же результаты, что и SameValue (сама отличается от строгого равенства тем, что возвращает истину для сравнения NaN с сам). SameValueZero используется в основном для проверки длины в массивах и им подобных, а в Map и Set для сравнения идентичности ключей и членов соответственно. Вывод: Map может использовать NaN в качестве ключа, а Set может иметь только один NaN; оба одинаково обрабатывают -0 и 0 как идентичные.

Много повторов. В определениях как строгого сравнения на равенство, так и алгоритмов SameValue отмечается, что SameValue (подробно описанный в этой спецификации) возвращает те же результаты, что и строгое сравнение на равенство, за исключением NaN и 0/-0. Определение SameValueZero (опять же, разъясненное полностью) отмечает, что оно возвращает те же результаты, что и SameValue, за исключением 0/-0 сравнений. Таким образом, это только один жестко закодированный пограничный случай, отличный от строгого равенства, но оба описаны полностью.

Также следует отметить, что, несмотря на то, что она определена как абстрактная операция, она используется в очень разговорной речи на протяжении всей спецификации: например. [если значения] оба ложны, то должно быть возвращено SameValue, а не […] затем возвращено SameValue(x, y), как вы могли бы ожидать . (Из 6.1.7.3 [[GetOwnProperty]]).

ES8 пересматривает использование всей партии, вводя SameValueNonNumber, от которого все, в конечном счете, зависит:

  • Абстрактное равенство остается неизменным, рекурсивно принуждая оба значения до тех пор, пока они не станут одного типа, а затем вызывая строгое равенство.
  • Строгое равенство проверяет один и тот же тип, проверяет подлинность самого числа (естественно, используя его особые случаи для нулей и NaN), затем вызывает SameValueNonNumber.
  • SameValue, как и строгое равенство, проверяет одинаковый тип, сравнивает числа (по своим правилам), затем вызывает SameValueNonNumber.
  • SameValueZero делает то же самое!
  • Наконец, SameValueNonNumber подтверждает, что оба входа имеют один и тот же тип, а не числа, а затем предоставляет полный список сравнений.

Все это намного суше и читается более логично. Это офигенно! Включите ES8.

Это все люди!

В этом есть несколько действительно сложных деталей, но если вы сможете усвоить хотя бы половину из них, вы, вероятно, будете звучать как гений для любых потенциальных работодателей. Хотя это и не исчерпывающе, я надеюсь, что это было понятно. Опять же, обратная связь приветствуется, если нет.

Я упустил некоторые детали, в частности, какое сравнение используется для методов встроенных объектов (например, Array.prototype.indexOf и т. д.), поэтому совет для профессионалов: если вы не знаете, избегайте NaN и -0, и ты будешь золотым. Как уже упоминалось, Map и Set используют SameValueNonZero. Прежде чем вы спросите об объектах, вычисленные имена свойств (т. е. выражения в квадратных скобках, используемые для доступа к свойствам) почти мгновенно преобразуются в строки с помощью ToPropertyKey.

Пожалуйста, дайте мне знать, если вы думаете о каких-либо других примерах или если я где-то ошибся. Точно так же дайте мне знать, если есть другая тема, которую вы хотели бы, чтобы я раскрыл так подробно. Я так люблю собирать головоломки. Надеюсь, я сделаю несколько, которые могут послужить небольшим ресурсом для интенсивного пересмотра.

Следующая тема, вероятно, посвящена более глубокому изучению чисел JavaScript, почему они не такие уж особенные (многие языки используют IEEE 754) и тому, как их безопасно использовать.