¿Tiene sentido usar == en JavaScript?

276

En JavaScript, The Good Parts , Douglas Crockford escribió:

JavaScript tiene dos conjuntos de operadores de igualdad: ===y !==, y sus gemelos malvados ==y !=. Los buenos funcionan de la manera que cabría esperar. Si los dos operandos son del mismo tipo y tienen el mismo valor, ===produce truey !==produce false. Los gemelos malvados hacen lo correcto cuando los operandos son del mismo tipo, pero si son de diferentes tipos, intentan forzar los valores. Las reglas por las cuales hacen eso son complicadas e inmemorables. Estos son algunos de los casos interesantes:

'' == '0'           // false
0 == ''             // true
0 == '0'            // true

false == 'false'    // false
false == '0'        // true

false == undefined  // false
false == null       // false
null == undefined   // true

' \t\r\n ' == 0     // true

La falta de transitividad es alarmante. Mi consejo es que nunca uses a los gemelos malvados. En cambio, siempre use ===y !==. Todas las comparaciones que acabamos de mostrar producen falsecon el ===operador.

Dada esta observación inequívoca, ¿hay algún momento en que el uso ==podría ser realmente apropiado?

Robert Harvey
fuente
11
Tiene sentido en muchos lugares. Cada vez que es obvio que está comparando dos cosas del mismo tipo (esto sucede mucho en mi experiencia), == vs === simplemente se reduce a la preferencia. Otras veces, realmente desea una comparación abstracta (como el caso que menciona en su respuesta). Si es apropiado depende de las convenciones para cualquier proyecto dado.
Hola
44
Con respecto a "muchos lugares", en mi experiencia, los casos en los que no importa superan en número a los casos en que sí. Tu experiencia puede ser diferente; quizás tengamos experiencia con diferentes tipos de proyectos. Cuando miro proyectos que usan ==por defecto, se ===destaca y me deja saber que algo importante está sucediendo.
Hola
44
No creo que JavaScript vaya lo suficientemente lejos con su tipo de coerción. Debería tener aún más opciones de coerción de tipo, al igual que el lenguaje BS .
Mark Booth
55
Un lugar que uso == es cuando comparo los ID desplegables (que siempre son char) con los ID del modelo (que comúnmente son int).
Scottie
12
El desarrollo web de @DevSolar tiene sentido cuando no desea tener que lidiar con la producción de una aplicación nativa para cada una de las 15 plataformas, así como la certificación en la tienda de aplicaciones de monopolio de cada plataforma que tiene una.
Damian Yerrick

Respuestas:

232

Voy a argumentar por ==

Douglas Crockford, que citó, es conocido por sus muchas y, a menudo, muy útiles opiniones. Mientras estoy con Crockford en este caso particular, vale la pena mencionar que no es la única opinión. Hay otros, como el creador de idiomas Brendan Eich, que no ven el gran problema ==. El argumento es un poco como el siguiente:

JavaScript es un lenguaje de tipo conductual *. Las cosas se tratan en función de lo que pueden hacer y no de su tipo real. Es por eso que puede llamar al .mapmétodo de una matriz en una NodeList o en un conjunto de selección jQuery. También es la razón por la que puede hacer 3 - "5"y recuperar algo significativo, porque "5" puede actuar como un número.

Cuando realiza una ==igualdad, está comparando el contenido de una variable en lugar de su tipo . Aquí hay algunos casos en los que esto es útil:

  • Leer un número del usuario : ¿leer el .valuede un elemento de entrada en el DOM? ¡No hay problema! No tiene que comenzar a lanzarlo o preocuparse por su tipo: puede ==hacerlo de inmediato en números y recuperar algo significativo.
  • ¿Necesita verificar la "existencia" de una variable declarada? - Puedes == nullhacerlo ya que el comportamiento nullrepresenta que no hay nada allí y que indefinido tampoco tiene nada allí.
  • ¿Necesita verificar si recibió un aporte significativo de un usuario? - compruebe si la entrada es falsa con el ==argumento, tratará los casos en que el usuario no haya ingresado nada o solo un espacio en blanco para usted, que probablemente sea lo que necesita.

Veamos los ejemplos de Crockford y explíquelos conductualmente:

'' == '0'           // got input from user vs. didn't get input - so false
0 == ''             // number representing empty and string representing empty - so true
0 == '0'            // these both behave as the number 0 when added to numbers - so true    
false == 'false'    // false vs got input from user which is truthy - so false
false == '0'        // both can substitute for 0 as numbers - so again true

false == undefined  // having nothing is not the same as having a false value - so false
false == null       // having empty is not the same as having a false value - so false
null == undefined   // both don't represent a value - so true

' \t\r\n ' == 0     // didn't get meaningful input from user vs falsey number - true 

Básicamente, ==está diseñado para funcionar en función de cómo se comportan las primitivas en JavaScript, no en función de lo que son . Si bien no estoy personalmente de acuerdo con este punto de vista, definitivamente vale la pena hacerlo, especialmente si tomas este paradigma de tratar tipos basados ​​en el comportamiento en todo el lenguaje.

* algunos podrían preferir el nombre de tipificación estructural, que es más común pero hay una diferencia, no estoy realmente interesado en discutir la diferencia aquí.

Benjamin Gruenbaum
fuente
8
Esta es una gran respuesta, y uso los tres casos de uso 'for =='. # 1 y # 3 son especialmente útiles.
Chris Cirefice
224
El problema ==no es que ninguna de sus comparaciones sea útil , es que las reglas son imposibles de recordar, por lo que es casi seguro que cometerás errores. Por ejemplo: "¿Necesita verificar si recibió una entrada significativa de un usuario?", Pero '0' es una entrada significativa y '0'==falsees verdadera. Si lo hubiera usado ===, habría tenido que pensar explícitamente en eso y no habría cometido el error.
Timmmm
44
"las reglas son imposibles de recordar" <== esta es la única cosa que me asusta de hacer algo "significativo" en Javascript. (y matemáticas flotantes que conducen a problemas en los ejercicios básicos de
cálculo
99
El 3 - "5"ejemplo plantea un buen punto en sí mismo: incluso si lo usa exclusivamente ===para comparación, esa es la forma en que las variables funcionan en Javascript. No hay forma de escapar por completo.
Jarett Millard
23
@Timmmm nota Estoy jugando al abogado del diablo aquí. No lo uso ==en mi propio código, me parece un antipatrón y estoy completamente de acuerdo en que el algoritmo de igualdad abstracta es difícil de recordar. Lo que estoy haciendo aquí es argumentar a favor de la gente.
Benjamin Gruenbaum
95

Resulta que jQuery usa la construcción

if (someObj == null) {
  // do something
}

ampliamente, como una abreviatura para el código equivalente:

if ((someObj === undefined) || (someObj === null))  {
  // do something
}

Esto es una consecuencia de la Especificación del lenguaje ECMAScript § 11.9.3, El algoritmo de comparación de igualdad abstracta , que establece, entre otras cosas, que

1.  If Type(x) is the same as Type(y), then  
    a.  If Type(x) is Undefined, return true.  
    b.  If Type(x) is Null, return true.

y

2.  If x is null and y is undefined, return true.
3.  If x is undefined and y is null, return true.

Esta técnica en particular es lo suficientemente común como para que JSHint tenga una bandera específicamente diseñada para ello.

Robert Harvey
fuente
10
No es justo responder tu propia pregunta. Quería responder esto :) == null or undefinedes el único lugar donde no uso ===o!==
favor, el
26
Para ser justos, jQuery no es una base de código modelo. Después de haber leído la fuente jQuery varias veces, es una de mis bases de código menos favoritas con muchos ternarios anidados, bits poco claros, anidamiento y cosas que de otro modo evitaría en el código real. Sin embargo, no confíe en mi palabra: léalo github.com/jquery/jquery/tree/master/src y luego compárelo con Zepto, que es un clon de jQuery: github.com/madrobby/zepto/tree/master/src
Benjamin Gruenbaum
44
También tenga en cuenta que Zepto parece tener un valor predeterminado ==y solo se usa ===en los casos en que es necesario: github.com/madrobby/zepto/blob/master/src/event.js
Hola
2
@Hey, para ser justos, Zepto tampoco es una base de código modelo: es infame por su uso __proto__y, a su vez, lo obliga casi sin ayuda a la especificación del lenguaje para evitar romper sitios web móviles.
Benjamin Gruenbaum
2
@BenjaminGruenbaum que no fue un juicio decisivo sobre la calidad de su base de código, solo señaló que diferentes proyectos siguen diferentes convenciones.
Hola
15

Comprobar valores para nullo undefinedes una cosa, como se ha explicado abundantemente.

Hay otra cosa, donde ==brilla:

Puede definir la comparación de la >=misma manera (la gente suele comenzar, >pero esto me parece más elegante):

  • a > b <=> a >= b && !(b >= a)
  • a == b <=> a >= b && b >= a
  • a < by a <= bse dejan como ejercicio para el lector.

Como sabemos, en JavaScript "3" >= 3y "3" <= 3, de donde obtienes 3 == "3". Puede señalar que es una idea horrible permitir la implementación de la comparación entre cadenas y números analizando la cadena. Pero dado que esta es la forma en que funciona, ==es absolutamente la forma correcta de implementar ese operador de relación.

Entonces, lo realmente bueno ==es que es consistente con todas las demás relaciones. Para decirlo de otra manera, si escribes esto:

function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  return 0;
}

Ya lo estás usando implícitamente ==.

Ahora a la pregunta bastante relacionada de: ¿Fue una mala elección implementar la comparación de números y cadenas de la forma en que se implementa? Visto de forma aislada, parece una cosa bastante estúpida. Pero en el contexto de otras partes de JavaScript y DOM, es relativamente pragmático, considerando que:

  • los atributos son siempre cadenas
  • las claves siempre son cadenas (el caso de uso es que use un Objectpara tener un mapa disperso de ints a valores)
  • La entrada del usuario y los valores de control de formulario son siempre cadenas (incluso si la fuente coincide input[type=number])

Por una gran cantidad de razones, tenía sentido hacer que las cadenas se comporten como números cuando sea necesario. Y suponiendo que la comparación de cadenas y la concatenación de cadenas tengan diferentes operadores (por ejemplo, ::para concatenar y un método para comparar (donde puede usar todo tipo de parámetros con respecto a mayúsculas y minúsculas), esto sería en realidad menos problemático. Pero esta sobrecarga del operador es, de hecho, de donde proviene el "Java" en "JavaScript";)

back2dos
fuente
44
Ser justo >=no es realmente transitivo. Es bastante posible en JS que ni a > bni a < bni b == a(por ejemplo:) NaN.
Benjamin Gruenbaum
8
@BenjaminGruenbaum: Eso es como decir +que no es realmente conmutativo, porque NaN + 5 == NaN + 5no se cumple. El punto es que >=funciona con valores numéricos para los cuales ==funciona de manera consistente. No debe sorprender que "no es un número" es, por su propia naturaleza no-ish número;)
back2dos
44
¿Entonces el mal comportamiento de ==es consistente con el mal comportamiento de >=? Genial, ahora desearía que hubiera un >==...
Eldritch Conundrum
2
@EldritchConundrum: Como he tratado de explicar, el comportamiento de >=es bastante consistente con el resto del lenguaje / API estándar. En su totalidad, JavaScript logra ser más que la suma de sus partes extravagantes. Si desea un >==, ¿también querría un estricto +? En la práctica, muchas de estas decisiones hacen que muchas cosas sean mucho más fáciles. Entonces no me apresuraría a juzgarlos como pobres.
back2dos
2
@EldritchConundrum: Nuevamente: los operadores de relación están destinados a comparar valores numéricos, donde un operando puede ser una cadena. Para los tipos de operandos para los que >=es significativo, ==es igualmente significativo, eso es todo. Nadie dice que deberías comparar [[]]con false. En lenguajes como C, el resultado de este nivel de tonterías es un comportamiento indefinido. Solo trátalo de la misma manera: no lo hagas. Y estarás bien. Tampoco necesitarás recordar ninguna regla mágica. Y luego, en realidad, es bastante sencillo.
back2dos
8

Como matemático profesional, veo en el operador de igualdad de Javscript == (también llamado "comparación abstracta", "igualdad suelta" ) un intento de construir una relación de equivalencia entre entidades, que incluye ser reflexivo , simétrico y transitivo . Desafortunadamente, dos de estas tres propiedades fundamentales fallan:

==no es reflexivo :

A == A puede ser falso, por ejemplo

NaN == NaN // false

==no es transitivo :

A == By B == Cjuntos no implican A == C, por ejemplo

'1' == 1 // true
1 == '01' // true
'1' == '01' // false

Solo la propiedad simétrica sobrevive:

A == Bimplica B == A, qué violación es probablemente impensable en cualquier caso y conduciría a una rebelión grave;)

¿Por qué importan las relaciones de equivalencia?

Porque ese es el tipo de relación más importante y frecuente, respaldado por numerosos ejemplos y aplicaciones. La aplicación más importante es la descomposición de entidades en clases de equivalencia , que es en sí misma una forma muy conveniente e intuitiva de entender las relaciones. Y no ser equivalencia conduce a la falta de clases de equivalencia, lo que a su vez conduce a la falta de intuición y complejidad innecesaria que es bien conocida.

¿Por qué es una idea tan terrible escribir ==para una relación de no equivalencia?

Porque rompe nuestra familiaridad e intuición, ya que literalmente cualquier relación interesante de similitud, igualdad, congruencia, isomorfismo, identidad, etc. es una equivalencia.

Conversión de tipo

En lugar de confiar en una equivalencia intuitiva, JavaScript introduce la conversión de tipos:

El operador de igualdad convierte los operandos si no son del mismo tipo, luego aplica una comparación estricta.

Pero, ¿cómo se define la conversión de tipo? ¿A través de un conjunto de reglas complicadas con numerosas excepciones?

Intento de construir una relación de equivalencia

Booleanos. Claramente truey falseno son lo mismo y deben estar en diferentes clases.

Números. Afortunadamente, la igualdad de números ya está bien definida, en la que dos números diferentes nunca están en la misma clase de equivalencia. En matemáticas, eso es. En JavaScript, la noción de número está algo deformada por la presencia de los más exóticos -0, Infinityy -Infinity. Nuestra intuición matemática dicta que 0y -0debe estar en la misma clase (de hecho lo -0 === 0es true), mientras que cada uno de los infinitos es una clase separada.

Números y booleanos. Dadas las clases de números, ¿dónde ponemos los booleanos? falsese vuelve similar a 0, mientras que se truevuelve similar a, 1pero no a otro número:

true == 1 // true
true == 2 // false

¿Hay alguna lógica aquí para truearmar 1? Es cierto que 1se distingue, pero también lo es -1. Yo personalmente no veo ninguna razón para convertir truea 1.

Y se pone aún peor:

true + 2 // 3
true - 1 // 0

Por truelo tanto, de hecho se convierte 1entre todos los números! ¿Es lógico? ¿Es intuitivo? La respuesta se deja como ejercicio;)

Pero qué hay de esto:

1 && true // true
2 && true // true

El único booleano xcon x && trueser truees x = true. Lo que demuestra que tanto 1y 2(y cualquier otro número que 0) se convierten a true! Lo que muestra es que nuestra conversión falla otra propiedad importante: ser biyección . Lo que significa que dos entidades diferentes pueden convertir a la misma. Lo cual, por sí mismo, no tiene que ser un gran problema. El gran problema surge cuando usamos esta conversión para describir una relación de "similitud" o "igualdad suelta" de lo que queramos llamarlo. Pero una cosa está clara: no será una relación de equivalencia y no se describirá intuitivamente a través de clases de equivalencia.

¿Pero podemos hacerlo mejor?

Al menos matemáticamente, ¡definitivamente sí! Se podría construir una relación de equivalencia simple entre booleanos y números con solo falsey 0estar en la misma clase. Entonces false == 0sería la única igualdad suelta no trivial.

¿Qué pasa con las cuerdas?

Podemos recortar cadenas de espacios en blanco al principio y al final para convertirlas en números, también podemos ignorar ceros al frente:

'   000 ' == 0 // true
'   0010 ' == 10 // true

Entonces obtenemos una regla simple para una cadena: recortar los espacios en blanco y los ceros al frente. O obtenemos un número o una cadena vacía, en cuyo caso la convertimos a ese número o cero. O no obtenemos un número, en cuyo caso no convertimos y, por lo tanto, no obtenemos una nueva relación.

¡De esta forma podríamos obtener una relación de equivalencia perfecta en el conjunto total de booleanos, números y cadenas! Excepto que ... los diseñadores de JavaScript obviamente tienen otra opinión:

' ' == '' // false

¡Entonces las dos cadenas a las que ambos se convierten de 0repente no son similares! ¿Por qué o por qué? De acuerdo con la regla, las cadenas son más o menos iguales precisamente cuando son estrictamente iguales. ¡No solo esta regla rompe la transitividad como vemos, sino que también es redundante! ¿Cuál es el punto de crear otro operador ==para que sea estrictamente idéntico al otro ===?

Conclusión

El operador de igualdad flexible ==podría haber sido muy útil si respetara algunas leyes matemáticas fundamentales. Pero como lamentablemente no, su utilidad sufre.

Dmitri Zaitsev
fuente
¿Qué hay de NaN? Además, a menos que se aplique un formato de número específico para la comparación con cadenas, debe resultar una comparación de cadenas no intuitiva o no transitividad.
Solomon Ucko
@SolomonUcko NaNactúa como mal ciudadano :-). La transitividad puede y debe mantenerse para cualquier comparación de equivalencia, intuitiva o no.
Dmitri Zaitsev
7

Sí, me he encontrado con un caso de uso, es decir, cuando se compara una clave con un valor numérico:

for (var key in obj) {
    var some_number = foo(key, obj[key]);  // or whatever -- this is just an example
    if (key == some_number) {
        blah();
    }
}

Creo que es mucho más natural realizar la comparación en key == some_numberlugar de como Number(key) === some_numbero como key === String(some_number).

Mehrdad
fuente
3

Me encontré con una aplicación bastante útil hoy. Si desea comparar números rellenos, como 01los enteros normales, ==funciona bien. Por ejemplo:

'01' == 1 // true
'02' == 1 // false

Le ahorra eliminar el 0 y convertirlo en un entero.

Jon Snow
fuente
44
Estoy bastante seguro de que la forma "correcta" de hacerlo es '04'-0 === 4, o posiblementeparseInt('04', 10) === 4
ratbum
No sabía que pudieras hacer esto.
Jon Snow
77
Lo entiendo mucho.
Jon Snow
1
@ratbum or+'01' === 1
Eric Lagergren
1
'011' == 011 // falseen modo no estricto y SyntaxError en modo estricto. :)
Brian S
3

Sé que esta es una respuesta tardía, pero parece que hay alguna posible confusión acerca de qué, nully en undefinedmi humilde opinión, es lo que hace el ==mal, más que la falta de transitividad, que es lo suficientemente malo. Considerar:

p1.supervisor = 'Alice';
p2.supervisor = 'None';
p3.supervisor = null;
p4.supervisor = undefined;

¿Qué significan estos?

  • p1 tiene un supervisor cuyo nombre es "Alicia".
  • p2 tiene un supervisor cuyo nombre es "Ninguno".
  • p3explícitamente, inequívocamente, no tiene un supervisor .
  • p4puede o puede tener un supervisor. No sabemos, no nos importa, se supone que no debemos saber (¿problema de privacidad?), Ya que no es asunto nuestro.

Cuando lo usas, ==estás combinando nully undefinedeso es totalmente inapropiado. ¡Los dos términos significan cosas completamente diferentes! ¡Decir que no tengo un supervisor simplemente porque me negué a decirte quién es mi supervisor está equivocado!

Entiendo que hay programadores a los que no les importa esta diferencia nully undefinedeligen usar estos términos de manera diferente. Y si su mundo no usa nully undefinedcorrectamente, o si desea dar su propia interpretación de estos términos, que así sea. Sin embargo, no creo que sea una buena idea.

Ahora, por cierto, no tengo ningún problema nully undefinedambos son falsos. Está perfectamente bien decir

if (p.supervisor) { ... }

y luego nully undefinedharía que el código que procesa el supervisor a ser omitido. Eso es correcto, porque no sabemos o no tenemos un supervisor. Todo bien. Pero las dos situaciones no son iguales . Por eso ==está mal. Una vez más, las cosas pueden ser falsas y usarse en un sentido de tipeo de pato, lo cual es ideal para lenguajes dinámicos. Es adecuado JavaScript, Pythonic, Rubyish, etc. Pero nuevamente, estas cosas NO son iguales.

Y no me refiero a no transitividad: "0x16" == 10, 10 == "10"pero no "10" == "0x16". Sí, JavaScript es débilmente tipos. Sí, es coercitivo. Pero la coercitividad nunca debería aplicarse a la igualdad.

Por cierto, Crockford tiene opiniones fuertes. ¿Pero sabes que? Él tiene razón aquí!

FWIW ¡Entiendo que existen, y personalmente me he encontrado con situaciones en las que ==es conveniente! Como tomar una entrada de cadena para números y, por ejemplo, compararla con 0. Sin embargo, esto es pirateo. Usted tiene la conveniencia de una compensación por un modelo incorrecto del mundo.

TL; DR: la falsedad es un gran concepto. No debe extenderse a la igualdad.

Ray Toal
fuente
Gracias por mostrar las diferentes situaciones :) Sin embargo, te estás perdiendo p5... la única situación typeof(p5.supervisor) === typeof(undefined)en la que el supervisor ni siquiera existe como concepto: D
TheCatWhisperer