В чем разница между (NaN! = NaN) и (NaN! == NaN)?

148

Прежде всего хочу отметить, что я умею isNaN()и Number.isNaN()работаю. Я читаю «Определенное руководство » Дэвида Фланагана, и он приводит пример того, как проверить, является ли значение NaN:

x !== x

Это приведет к тому, trueесли и только если xесть NaN.

Но теперь у меня вопрос: почему он использует строгое сравнение? Потому что кажется, что

x != x

ведет себя так же. Безопасно ли использовать обе версии, или мне не хватает некоторых значений в JavaScript, которые будут возвращаться trueдля x !== xи falseдля x != x?

Георгий Накери
источник
10
Может быть , что Фланаган просто предпочитает !==чеки более !=чеков. Насколько я знаю, нет другой ценности, где x != x. Но есть две разные группы разработчиков JavaScript: те, кто предпочитает, !=и те, кто предпочитает !==, будь то скорость, ясность, выразительность и т. Д.
Стив Клостерс
30
Зачем использовать свободное сравнение, если строгое сравнение ведет себя так же?
Ry-
3
@Raulucco: NaNэто не уникальный тип, это число. Это уникальная ценность, которая не равна себе.
TJ Crowder
8
Название, кажется, вводит в заблуждение людей. Я бы предложил изменить его на что-то вроде "Отличается ли x! = X от x! == x?"
TJ Crowder
6
@femmestem: Георгий сказал, что «в этом случае» это вопрос стиля. И он прав в этом. Это не стиль , когда типы операндов различны, но это стиль , когда они одинаковы. Отдельно: Фланаган проводит эти сравнения ===с NaN, чтобы подчеркнуть, что NaN не равен самому себе. Он не «неправ», он делает это как учебное упражнение, демонстрируя, что это не работает.
TJ Crowder

Ответы:

128

Во-первых, позвольте мне отметить, что NaNэто особая ценность: по определению она не равна самой себе. Это происходит из стандарта IEEE-754, на котором основаны числа JavaScript. Значение «не число» никогда не равно само себе, даже если биты точно совпадают. (В IEEE-754 они не обязательно, он допускает несколько разных значений «не число».) Вот почему это даже подходит; все остальные значения в JavaScript равны сами по себе, NaNпросто особенные.

... мне не хватает некоторого значения в JavaScript, которое будет возвращать true для x! == x и false для x! = x?

Нет, ты не Единственная разница между !==и !=заключается в том, что последний будет выполнять приведение типов в случае необходимости, чтобы типы операндов были одинаковыми. В x != x, типы операндов одинаковы, и поэтому он точно так же, как x !== x.

Это ясно с самого начала определения операции абстрактного равенства :

  1. ReturnIfAbrupt (х).
  2. ReturnIfAbrupt (у).
  3. Если тип (х) совпадает с типом (у), то

    Вернуть результат выполнения Строгое Сравнения Равенства x === y.

  4. ...

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

Так что, если Фланаган прав, что только NaNдаст правду x !== x, мы можем быть уверены, что это также верно, что только NaNдаст правду x != x.

Многие программисты на JavaScript по умолчанию используют ===и !==избегают некоторых ловушек вокруг приведения типов, которые делают свободные операторы, но в этом случае нечего читать при использовании Фланаганом оператора строгого и свободного.

TJ Crowder
источник
Я перечитал 4.9.1 - Equality and Inequality Operatorsраздел, и это, кажется, ответ. Ключевой момент для ===сравнения: If the two values have the same type, test them for strict equality as described above. If they are strictly equal, they are equal. If they are not strictly equal, they are not equal.
Георгий Накери
@GiorgiNakeuri: Я не уверен, на какую версию 4.9.1 вы ссылаетесь, возможно, на книгу Фланагана? Но это в основном говорит то, что говорит цитата из спецификации выше, да.
TJ Crowder
2
Я принимаю это, потому что это отвечает на мой вопрос формализованно и точно. Спасибо за объяснения!
Георгий Накери
1
@Moshe: Что вы подразумеваете под "живыми привязками"? (Термин не указан в спецификации.) Вы имеете в виду что-то вроде примера GOTO 0, где aна самом деле функция и не возвращает одно и то же значение дважды? Это не то же самое, что значение, для которого !==было бы верно, о чем спрашивал ОП. Это просто функция, которая возвращает разные значения. foo() !== foo()не обязательно также верно, так как fooможет возвращать разные значения при каждом вызове.
TJ Crowder
1
@Moshe Ну, это супер противный способ связываться со свойствами и добытчиками. Но это похоже на пример GOTO 0, только с дополнительным уровнем косвенности.
JAB
37

Для целей NaN !=и !==сделать то же самое.

Однако многие программисты избегают ==или используют !=JavaScript. Например, Дуглас Крокфорд считает их « плохими частями » языка JavaScript, потому что они ведут себя неожиданным и запутанным образом:

В JavaScript есть два набора операторов равенства: ===и !==, и их злые близнецы ==и !=. Хорошие работают так, как вы ожидаете.

... Мой совет - никогда не использовать злых близнецов. Вместо этого всегда используйте ===и !==.

jkdev
источник
2
Вопрос не о NaN (несмотря на название). Вопрос в том, "не хватает ли я в JavaScript значения, которое вернет true для x! == x и false для x! = X?"
TJ Crowder
@TJCrowder Два вопроса, правда. Первый вопрос: «Безопасно ли использовать обе версии», и ответ заключается в том, что обе версии эквивалентны. Мне нравится ваш ответ «под капотом», который объясняет все подробно.
jkdev
22

Просто для удовольствия, позвольте мне показать вам искусственный пример, где xнет, NaNно операторы в любом случае ведут себя по-разному. Сначала определите:

Object.defineProperty(
  self,
  'x',
  { get: function() { return self.y = self.y ? 0 : '0'; } }
);

Тогда у нас есть

x != x // false

но

x !== x // true
GOTO 0
источник
9
Ха! :-) Но это эффективно, foo() != foo()когда foo возвращает 1, а затем 2. Например, значения не совпадают, просто сравниваются разные значения.
TJ Crowder
2

Я просто хочу отметить, что NaNэто не единственное, что производит x !== xбез использования глобального объекта. Есть много умных способов вызвать это поведение. Вот один, использующий геттеры:

var i = 0, obj = { get x() { return i++; }};
with(obj) // force dynamic context, this is evil. 
console.log(x === x); // false

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

По какой-то причине, кроме меня, люди считают, что это проблема с JS, но большинство языков с двойными значениями (а именно, C, Java, C ++, C #, Python и другие) демонстрируют это точное поведение, и люди просто в порядке.

Бенджамин Грюнбаум
источник
2
Да, именно это @TJCrowder упомянул в комментарии к ответу GOTO_0, не так ли?
Георгий Накери
Не могли бы вы уточнить, как получить неоднозначное приведение типов в этих других языках?
chicocvenancio
0

Поскольку иногда изображения лучше, чем слова, проверьте эту таблицу (поэтому я хочу сделать это ответом вместо комментария, потому что он лучше виден).

Там вы можете увидеть, что строгое сравнение равенства (===) возвращает true только если тип и содержимое совпадают, поэтому

var f = "-1" === -1; //false

В то время как сравнение абстрактного равенства (==) проверяет только содержимое *, преобразовывая типы и затем строго сравнивая их:

var t = "-1" == -1; //true

Хотя не понятно, без консультации с ECMA , что учитывает JavaScript при сравнении, таким образом, что код ниже оценивает как true.

 var howAmISupposedToKnowThat = [] == false; //true
MVCDS
источник