Jaka jest różnica między (NaN! = NaN) a (NaN! == NaN)?

148

Przede wszystkim chcę wspomnieć, że umiem isNaN()i Number.isNaN()pracuję. Czytam The Definite Guide Davida Flanagana i podaje on przykład, jak sprawdzić, czy wartość wynosi NaN:

x !== x

Spowoduje to truewtedy i tylko wtedy, gdy xjest NaN.

Ale teraz mam pytanie: dlaczego używa ścisłego porównania? Ponieważ na to wygląda

x != x

zachowuje się w ten sam sposób. Czy używanie obu wersji jest bezpieczne, czy też brakuje mi pewnych wartości w JavaScript, które zostaną zwrócone truedla x !== xi falsedla x != x?

Giorgi Nakeuri
źródło
10
Możliwe, że Flanagan po prostu woli !==czeki od !=czeków. O ile mi wiadomo, nie ma innej wartości, w której x != x. Ale są dwie odrębne grupy programistów JavaScript: ci, którzy wolą !=i ci, którzy wolą !==, czy to ze względu na szybkość, przejrzystość, ekspresję itp.
Steve Klösters
30
Po co używać luźnych porównań, skoro ścisłe porównanie zachowuje się w ten sam sposób?
Ry-
3
@Raulucco: NaNnie jest unikalnym typem, to liczba. To wyjątkowa wartość, która nie jest sobie równa.
TJ Crowder
8
Tytuł wydaje się być mylący. Proponuję zmienić to na coś w rodzaju „Czy x! = X jest kiedykolwiek różny od x! == x?”
TJ Crowder
6
@femmestem: Giorgi powiedział, że „w tym przypadku” to kwestia stylu. I ma w tym rację. To nie jest styl, gdy typy operandów są różne, ale to jest styl, gdy są takie same. Osobno: Flanagan dokonuje tych porównań ===z NaN, aby podkreślić, że NaN nie jest sobie równy. Nie jest „zły”, robi to jako ćwiczenie dydaktyczne, demonstrując, że to nie działa.
TJ Crowder,

Odpowiedzi:

128

Po pierwsze, chciałbym zaznaczyć, że NaNjest to bardzo szczególna wartość: z definicji nie jest sobie równa. Wynika to ze standardu IEEE-754, z którego korzystają numery JavaScript. Wartość „nie jest liczbą” nigdy nie jest równa sobie, nawet jeśli bity są dokładnie zgodne. (Które niekoniecznie w IEEE-754, zezwala na wiele różnych wartości „nie jest liczbą”). Dlatego właśnie to się pojawia; wszystkie inne wartości w JavaScript są sobie równe, NaNsą po prostu wyjątkowe.

... czy brakuje mi jakiejś wartości w JavaScript, która zwróci prawdę dla x! == x i fałsz dla x! = x?

Nie, nie jesteś. Jedyna różnica między !==i !=polega na tym, że ten ostatni wykona wymuszenie typu, jeśli to konieczne, aby uzyskać takie same typy operandów. W x != xprogramie typy operandów są takie same, więc jest dokładnie takie samo, jak x !== x.

Jest to jasne od początku definicji Operacji Abstrakcyjnej Równości :

  1. ReturnIfAbrupt (x).
  2. ReturnIfAbrupt (y).
  3. Jeśli Type (x) jest taki sam jak Type (y), to

    Zwróć wynik wykonywania Ścisłego porównania równości x === y.

  4. ...

Pierwsze dwa kroki to podstawowa instalacja hydrauliczna. W efekcie pierwszym krokiem ==jest sprawdzenie, czy typy są takie same, a jeśli tak, to ===zamiast tego zrobić . !=i !==są po prostu zanegowanymi wersjami tego.

Więc jeśli Flanagan ma rację, że tylko NaNda prawdę x !== x, możemy być pewni, że prawdą jest również to, że tylko NaNda prawdę x != x.

Wielu programistów JavaScript domyślnie używa ===i !==unika pewnych pułapek związanych z wymuszaniem typów, jakie stosują operatory loose, ale nie ma nic do czytania w używaniu przez Flanagana operatora ścisłego i luźnego w tym przypadku.

TJ Crowder
źródło
Ponownie przeczytałem 4.9.1 - Equality and Inequality Operatorssekcję i wydaje się, że to jest odpowiedź. Kluczową kwestią dla ===porównania jest: 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.
Giorgi Nakeuri
@GiorgiNakeuri: Nie jestem pewien, o czym mowa w 4.9.1, może o książce Flanagana? Ale to w zasadzie mówi o tym, co mówi cytat z powyższej specyfikacji, tak.
TJ Crowder
2
Akceptuję to, ponieważ to odpowiada na moje pytanie w sformalizowany i precyzyjny sposób. Dziękuję za wyjaśnienia!
Giorgi Nakeuri
1
@Moshe: Co masz na myśli mówiąc „na żywo”? (Termin nie pojawia się w specyfikacji.) Czy masz na myśli coś takiego jak przykład GOTO 0, gdzie w arzeczywistości jest funkcją i nie zwraca dwukrotnie tej samej wartości? To nie to samo, co wartość, która !==byłaby prawdziwa, o co pytał PO. To po prostu funkcja, która zwraca różne wartości. foo() !== foo()nie jest też koniecznie prawdą, ponieważ foomoże zwracać różne wartości przy każdym wywołaniu.
TJ Crowder
1
@Moshe Cóż, to super paskudny sposób na bałagan z właściwościami i getterami. Ale wydaje się, że jest prawie taki sam jak przykład GOTO 0, tylko z dodatkową warstwą pośrednictwa.
JAB
37

Dla celów NaN !=i !==zrób to samo.

Jednak wielu programistów unika ==lub !=używa JavaScript. Na przykład Douglas Crockford uważa je za „ złe strony ” języka JavaScript, ponieważ zachowują się w nieoczekiwany i mylący sposób:

JavaScript ma dwa zestawy operatorów równości: ===and !==, oraz ich złe bliźniaki ==i !=. Te dobre działają zgodnie z oczekiwaniami.

... Moja rada brzmi: nigdy nie używaj złych bliźniaków. Zamiast tego zawsze używaj ===i !==.

jkdev
źródło
2
Pytanie nie dotyczy NaN (pomimo tytułu). Pytanie brzmi: „Czy brakuje mi jakiejś wartości w JavaScript, która zwróci wartość true dla x! == x i false dla x! = X?”
TJ Crowder
@TJCrowder Tak naprawdę dwa pytania. Pierwsze pytanie brzmi: „Czy używanie obu wersji jest bezpieczne”, a odpowiedź brzmi, że obie wersje są równoważne. Podoba mi się twoja odpowiedź „pod maską”, która wyjaśnia wszystko szczegółowo.
jkdev
22

Dla zabawy pozwólcie, że pokażę wam sztuczny przykład, gdzie go xnie ma, NaNale operatorzy i tak zachowują się inaczej. Najpierw zdefiniuj:

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

Następnie mamy

x != x // false

ale

x !== x // true
GOTO 0
źródło
9
Ha! :-) Ale w praktyce foo() != foo()foo zwraca 1, a potem 2. Np. Wartości nie są takie same, po prostu porównuje różne wartości.
TJ Crowder,
2

Chcę tylko podkreślić, że NaNnie jest jedyną rzeczą, która produkuje x !== xbez użycia obiektu globalnego. Istnieje wiele sprytnych sposobów wywołania tego zachowania. Oto jeden z pobierających:

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

Jak wskazują inne odpowiedzi, ==wykonuje koersję typów, ale tak jak w innych językach i par standard - NaN wskazuje na błąd obliczeniowy iz dobrych powodów nie jest sobie równy.

Z jakiegoś niezrozumiałego powodu ludzie uważają, że jest to problem z JS, ale większość języków, które mają dublety (a mianowicie C, Java, C ++, C #, Python i inne) wykazuje dokładnie to zachowanie i ludziom po prostu nie przeszkadza.

Benjamin Gruenbaum
źródło
2
Tak, właśnie o tym @TJCrowder wspomniał w komentarzu do odpowiedzi GOTO_0, prawda?
Giorgi Nakeuri
Czy mógłbyś wyjaśnić, jak uzyskać niejednoznaczny wymuszenie typu w tych innych językach?
chicocvenancio
0

Ponieważ czasami obrazy są lepsze niż słowa, sprawdź tę tabelę (dlatego uważam, że jest to odpowiedź, zamiast komentarza, ponieważ staje się ona lepiej widoczna).

Możesz tam zobaczyć, że ścisłe porównanie równości (===) zwraca prawdę tylko wtedy, gdy typ i zawartość są zgodne, więc

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

Podczas gdy abstrakcyjne porównanie równości (==) sprawdza tylko zawartość *, konwertując typy, a następnie ściśle je porównując:

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

Chociaż nie jest jasne, bez konsultacji z ECMA , co JavaScript bierze pod uwagę podczas porównywania, w taki sposób, że poniższy kod ocenia jako prawdziwy.

 var howAmISupposedToKnowThat = [] == false; //true
MVCDS
źródło