¿Cuál es la diferencia entre (NaN! = NaN) y (NaN! == NaN)?

148

Antes que nada quiero mencionar que sé cómo isNaN()y cómo Number.isNaN()trabajo. Estoy leyendo La guía definitiva de David Flanagan y él da un ejemplo de cómo verificar si el valor es NaN:

x !== x

Esto dará como resultado truesi y solo si xes así NaN.

Pero ahora tengo una pregunta: ¿por qué usa una comparación estricta? Porque parece que

x != x

se comporta de la misma manera. ¿Es seguro usar ambas versiones, o me faltan algunos valores en JavaScript que devolverán truepor x !== xy falsepara x != x?

Giorgi Nakeuri
fuente
10
Podría ser que Flanagan simplemente prefiera !==cheques sobre !=cheques. Que yo sepa, no hay otro valor donde x != x. Pero hay dos grupos distintos de desarrolladores de JavaScript: los que prefieren !=y los que prefieren !==, ya sea por velocidad, claridad, expresividad, etc.
Steve Klösters
30
¿Por qué usar una comparación flexible cuando la comparación estricta se comporta de la misma manera?
Ry-
3
@ Raulucco: NaNno es un tipo único, es un número. Es un valor único que no es igual a sí mismo.
TJ Crowder
8
El título parece ser gente engañosa. Sugeriría cambiarlo a algo como "¿Es x! = X alguna vez diferente de x! == x?"
TJ Crowder
66
@femmestem: Giorgi dijo "en este caso" es una cuestión de estilo. Y tiene razón en eso. No es estilo cuando los tipos de operandos son diferentes, pero es estilo cuando son iguales. Por separado: Flanagan está haciendo esas comparaciones ===con NaN para señalar que NaN no es igual a sí mismo. No está "equivocado", lo está haciendo como un ejercicio de enseñanza, demostrando que no funciona.
TJ Crowder

Respuestas:

128

Primero, permítanme señalar que NaNes un valor muy especial: por definición, no es igual a sí mismo. Eso proviene del estándar IEEE-754 en el que se basan los números de JavaScript. El valor "no es un número" nunca es igual a sí mismo, incluso cuando los bits coinciden exactamente. (Que no son necesariamente en IEEE-754, permite múltiples valores diferentes "no un número"). Es por eso que incluso aparece; todos los demás valores en JavaScript son iguales a sí mismos, NaNes simplemente especial.

... ¿me falta algún valor en JavaScript que devuelva verdadero para x! == x y falso para x! = x?

No tu no eres. La única diferencia entre !==y !=es que este último hará una coerción de tipo si es necesario para que los tipos de operandos sean los mismos. En x != x, los tipos de los operandos son los mismos, por lo que es exactamente el mismo que x !== x.

Esto queda claro desde el comienzo de la definición de la Operación de igualdad abstracta :

  1. ReturnIfAbrupt (x).
  2. ReturnIfAbrupt (y).
  3. Si Tipo (x) es lo mismo que Tipo (y), entonces

    Devuelve el resultado de realizar una estricta comparación de igualdad x === y.

  4. ...

Los primeros dos pasos son fontanería básica. En efecto, el primer paso ==es ver si los tipos son los mismos y, si es así, hacer en su ===lugar. !=y !==son solo versiones negadas de eso.

Entonces, si Flanagan está en lo correcto, eso solo NaNdará verdad para x !== x, podemos estar seguros de que también es cierto que solo NaNdará verdad por x != x.

Muchos programadores de JavaScript usan de manera predeterminada ===y !==evitan algunas trampas en torno a la coerción de tipo que hacen los operadores sueltos, pero no hay nada que leer sobre el uso de Flanagan del operador estricto frente al operador suelto en este caso.

TJ Crowder
fuente
He vuelto a leer la 4.9.1 - Equality and Inequality Operatorssección y esta parece ser la respuesta. El punto clave para la ===comparación es: 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: No estoy seguro de a qué se refiere 4.9.1, ¿quizás el libro de Flanagan? Pero eso básicamente dice lo que dice la cita de la especificación anterior, sí.
TJ Crowder
2
Estoy aceptando esto porque responde a mi pregunta de manera formal y precisa. Gracias por las explicaciones!
Giorgi Nakeuri
1
@Moshe: ¿Qué quieres decir con "enlaces en vivo"? (El término no aparece en la especificación). ¿Se refiere a algo como el ejemplo de GOTO 0 donde en arealidad es una función y no devuelve el mismo valor dos veces? Eso no es lo mismo que un valor para el cual !==sería cierto, que es lo que preguntó el OP. Es solo una función que devuelve diferentes valores. foo() !== foo()tampoco es necesariamente cierto, ya que foopodría devolver valores diferentes en cada llamada.
TJ Crowder
1
@Moshe Bueno, esa es una forma muy desagradable de meterse con las propiedades y los captadores. Pero parece ser más o menos lo mismo que el ejemplo de GOTO 0, solo que con una capa adicional de indirección.
JAB
37

Para fines de NaN, !=y !==hacer lo mismo.

Sin embargo, muchos programadores evitan ==o !=en JavaScript. Por ejemplo, Douglas Crockford los considera entre las " partes malas " del lenguaje JavaScript porque se comportan de maneras inesperadas y confusas:

JavaScript tiene dos conjuntos de operadores de igualdad: ===y !==, y sus gemelos malvados ==y !=. Los buenos funcionan de la manera que cabría esperar.

... Mi consejo es que nunca uses a los gemelos malvados. En cambio, siempre use ===y !==.

jkdev
fuente
2
La pregunta no es sobre NaN (a pesar del título). La pregunta es "¿Me falta algún valor en JavaScript que devuelva verdadero para x! == x y falso para x! = X?"
TJ Crowder
@TJCrowder Dos preguntas, de verdad. La primera pregunta es "¿Es seguro usar ambas versiones" y la respuesta es que ambas versiones son equivalentes. Me gusta su respuesta "bajo el capó" que explica todo en detalle.
jkdev
22

Solo por diversión, permíteme mostrarte un ejemplo artificial donde xno está NaNpero los operadores se comportan de manera diferente de todos modos. Primero defina:

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

Entonces nosotros tenemos

x != x // false

pero

x !== x // true
GOTO 0
fuente
9
¡Decir ah! :-) Pero eso es efectivamente foo() != foo()donde foo devuelve 1 y luego 2. Por ejemplo, los valores no son los mismos, solo compara diferentes valores.
TJ Crowder
2

Solo quiero señalar que NaNno es lo único que produce x !== xsin usar el objeto global. Hay muchas formas inteligentes de desencadenar este comportamiento. Aquí hay uno que usa getters:

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

Como señalan otras respuestas, ==realiza una coerción de tipo, pero como en otros idiomas y a la par del estándar: NaN indica un error de cálculo y, por buenas razones, no es igual a sí mismo.

Por alguna razón más allá de mí, la gente considera que este es un problema con JS, pero la mayoría de los lenguajes que tienen dobles (es decir, C, Java, C ++, C #, Python y otros) exhiben este comportamiento exacto y la gente está bien con eso.

Benjamin Gruenbaum
fuente
2
Sí, eso es exactamente lo que @TJCrowder ha mencionado en el comentario a la respuesta de GOTO_0, ¿no?
Giorgi Nakeuri
¿Podría aclarar cómo obtener la coerción de tipo ambiguo en estos otros idiomas?
chicocvenancio
0

Como a veces, las imágenes son mejores que las palabras, revise esta tabla (que es la razón por la que hago de esto una respuesta en lugar de un comentario es porque obtiene una mejor visibilidad).

Allí puede ver que la comparación de igualdad estricta (===) solo devuelve verdadero si el tipo y el contenido coinciden, por lo que

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

Mientras que la comparación de igualdad abstracta (==) verifica solo el contenido * al convertir tipos y luego compararlos estrictamente:

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

Aunque no está claro, sin consultar a ECMA , qué considera JavaScript al comparar, de una manera que el siguiente código evalúa como verdadero.

 var howAmISupposedToKnowThat = [] == false; //true
MVCDS
fuente