Como todos sabemos, == en JavaScript es una operación relativamente compleja. Sus reglas de operación son muy extrañas y pueden cometer errores fácilmente, lo que lo convierte en una de las "peores características" en JavaScript.
Basado en leer cuidadosamente la especificación de ECMAScript, dibujé una imagen. Pensé que después de entender esta imagen, comprenderá a fondo todo sobre la operación ==. Al mismo tiempo, traté de demostrarle a todos a través de este artículo que == no es tan malo. Es fácil dominar e incluso parece razonable y no tan malo.
Primero, la imagen:
== La descripción exacta de las reglas de operación está aquí: el algoritmo de comparación de igualdad abstracta. Sin embargo, con una descripción tan complicada, ¿estás seguro de que no te sentirás mareado después de leerlo? ¿Puedes usarlo para guiar tu práctica de inmediato?
Ciertamente no funciona. Después de todo, la especificación es para los desarrolladores del entorno de ejecución de JavaScript (en comparación con los desarrolladores de motores V8), no para usuarios de idiomas. La imagen de arriba traduce la especificación en un formulario que sea conveniente para que todos lo vean.
Antes de introducir cada parte en la Figura 1 en detalle, revisemos el conocimiento sobre los tipos en JS:
Hay dos tipos de valores en JS: tipo básico y tipo de objeto.
Los tipos básicos incluyen: indefinido, nulo, booleano, número y cadena.
Tanto el tipo indefinido como el tipo nulo tienen solo un valor, a saber, indefinido y nulo; El tipo booleano tiene dos valores: Verdadero y Falso; Hay muchos valores del tipo de número; y el tipo de cadena tiene innumerables valores (teóricamente).
Todos los objetos tienen métodos ValueOf () y toString (), que se heredan del objeto y, por supuesto, pueden reescribirse por subclases.
Ahora considere la expresión:
x == Y
donde x e y son valores de uno de los seis tipos.
Cuando los tipos de X e Y son los mismos, x == Y se puede convertir en x === Y, y este último es muy simple (lo único que debe tener en cuenta es Nan), por lo que a continuación solo consideramos los casos en que los tipos de X e Y son diferentes.
1. Tener y ninguno
En la Figura 1, seis tipos de valores de JavaScript están representados por rectángulos con fondo azul. Primero se dividen en dos grupos:
Cadena, número, booleano y objeto (correspondiente a la caja rectangular grande a la izquierda)
Indefinido y nulo (correspondiente a la caja rectangular a la derecha)
¿Cuál es la base para la agrupación? Echemos un vistazo. Undefinado y nulo a la derecha se usan para indicar incertidumbre, no o vacías, mientras que los cuatro tipos de la derecha están determinados, existentes y no vacíos. Podemos decir esto:
A la izquierda hay un mundo de existencia, y a la derecha hay un mundo vacío.
Por lo tanto, es razonable comparar cualquier valor en los dos mundos con falsos. (es decir, la línea horizontal que conecta dos rectángulos en la Figura 1 está marcado Falso)
2. Vacío y vacío
Undefinado y nulo en JavaScript son otro lugar que a menudo nos bloquea. Por lo general, se considera un defecto de diseño, en el que no cavaremos. Pero he oído que el autor de JavaScript inicialmente pensó esto:
Si planea asignar una variable al valor del tipo de objeto, pero aún no ha asignado un valor, entonces puede usar NULL para representar el estado en este momento (una de las pruebas es que el resultado de typeOf null es 'objeto'); Por el contrario, si planea asignar una variable al valor del tipo original, pero aún no ha asignado un valor, entonces puede usar indefinido para representar el estado en este momento.
Independientemente de si este rumor es creíble o no, es razonable que el resultado de la comparación entre los dos sea cierto. (es decir, verdadero marcado en la línea vertical a la derecha en la Figura 1)
Antes de pasar al siguiente paso, hablemos sobre los dos símbolos en la Figura 1: mayúsculas n y P. Estos dos símbolos no significan positivos y negativos en la sección PN. En cambio:
N representa la operación Tonumber, lo que significa convertir el operando en un número. Es una operación abstracta en la especificación ES, pero podemos usar la función Número () en JS para reemplazarla de manera equivalente.
P representa la operación toprimitiva, es decir, convertir el operando al valor del tipo original. También es una operación abstracta en la especificación ES, y también se puede traducir en código JS equivalente. Pero es un poco más complicado. Para decirlo, para un objeto obj:
Toprimitivo (obj) es equivalente a: calcular primero obj.valueOf (), si el resultado es el valor original, este resultado se devuelve; De lo contrario, se calcula obj.ToString (), y si el resultado es el valor original, se devuelve este resultado; De lo contrario, se lanza una excepción.
Nota: Hay una excepción aquí, es decir, una fecha de objeto de tipo, que primero llamará al método toString ().
En la Figura 1, una línea marcada N o P indica que cuando los dos tipos de datos están conectados para realizar la operación ==, el operando en el lado marcado N o P primero debe realizar la transformación Tonumber o Toprimitiva.
3. Verdadero y falso
Como se puede ver en la Figura 1, cuando se compara un valor booleano con otros tipos de valores, el valor booleano se convierte en un número. Específicamente
Verdadero -> 1
falso -> 0
Esto no requiere demasiado abuso verbal en absoluto. Piénselo, en C, no hay tipo booleano en absoluto. Los enteros 1 y 0 generalmente se usan para representar la lógica verdadera o falsa.
IV. Secuencia de caracteres
En la Figura 1, dividimos la cadena y el número en un grupo. ¿Por qué? Entre los seis tipos, la cadena y el número son secuencias de caracteres (al menos literalmente). Una cadena es una secuencia de todos los caracteres legales, mientras que un número puede considerarse como una secuencia de caracteres que cumplen con ciertas condiciones. Por lo tanto, los números pueden considerarse como un subconjunto de cadenas.
Según la Figura 1, al realizar la operación == de las cadenas y los números, debe usar la operación Tonumber para convertir la cadena en números. Suponiendo que x es una cadena e y es un número, luego:
x == y -> número (x) == Y
Entonces, ¿cuál es la regla para convertir cadenas en números? La especificación se describe de una manera muy complicada, pero en términos generales, es eliminar las citas en ambos lados de la cadena y ver si puede formar un número legal. Si es así, el resultado de conversión es este número; De lo contrario, el resultado es Nan. Por ejemplo:
Número ('123') // Resultado 123
Número ('1.2e3') // Resultado 1200
Número ('123ABC') // Resultado nan
Por supuesto, hay excepciones, como el resultado de convertir una cadena vacía en un número es 0. En este momento
Número ('') // Resultado 0
V. Simple y complejo
Los tipos primitivos son tipos simples, son sencillos y fáciles de entender. Sin embargo, la desventaja es que la capacidad de expresión es limitada y difícil de expandir, por lo que hay objetos. Un objeto es una colección de atributos, y el atributo en sí puede ser un objeto. Por lo tanto, los objetos se pueden construir arbitrariamente lo suficientemente complejos como para representar varias cosas.
Pero a veces las cosas son complicadas y no son algo bueno. Por ejemplo, no todos tienen el tiempo, la paciencia o la necesidad de leerlo de principio a fin. Por lo general, es suficiente para comprender solo sus pensamientos centrales. Entonces, el documento tiene palabras clave y descripciones. Lo mismo es cierto para los objetos en JavaScript. Necesitamos tener un medio para comprender sus características principales, por lo que los objetos tienen métodos toString () y valueOf ().
El método toString () se usa para obtener una descripción de texto del objeto; y el método ValueOf () se usa para obtener el valor propio del objeto.
Por supuesto, este es solo mi propio entendimiento. Además, como su nombre lo indica, el método toString () tiende a devolver una cadena. ¿Qué pasa con el método ValueOf ()? Según la descripción de la especificación, tiende a devolver un número, aunque en el tipo incorporado, el método ValueOf () devuelve solo el número y la fecha.
Según la Figura 1, cuando un objeto se compara con un objeto no objeto, el objeto debe convertirse en un tipo primitivo (aunque al comparar con un tipo booleano, el tipo booleano debe convertirse en un tipo numérico primero, pero el tipo de objeto debe convertirse en un tipo primitivo siguiente). Esto también es razonable. Después de todo, == no es una comparación estrictamente igual. Solo necesitamos eliminar las características principales del objeto para participar en la operación y dejar a un lado las características secundarias.
Seis. Todo se cuenta
Veamos hacia atrás a la Figura 1. Las líneas marcadas N o P dentro no tienen dirección. Si marcamos las flechas en estas líneas, los puntos de conexión desde el extremo marcados N o P al otro extremo, entonces obtendremos (sin considerar indefinidos y nulos):
¿Has descubierto algo? Sí, durante el proceso de cálculo, todos los tipos de valores tienden a convertirse en tipos numéricos. Después de todo, una celebridad dijo una vez:
Todo se cuenta.
7. Solo dame una castaña
Hay demasiadas tonterías en el pasado, por lo que aquí hay un ejemplo para demostrar que la Figura 1 es realmente conveniente y efectiva para guiar la práctica.
Ejemplo, calcule lo siguiente:
[''] == Falso
Primero, los dos operandos son de tipo de objeto y tipo booleano respectivamente. Según la Figura 1, es necesario convertir el tipo booleano en un tipo numérico, y el resultado de convertir falso a un numérico es 0, por lo que la expresión se convierte en:
[''] == 0
Los dos operandos se convierten en tipo de objeto y tipo numérico. Según la Figura 1, el tipo de objeto debe convertirse al tipo original:
Primero, llame [] .ValueOf (). Dado que el método ValueOf () de la matriz se devuelve en sí mismo, el resultado no es el tipo original. Continúe llamando [] .ToString ().
Para las matrices, el algoritmo del método toString () es convertir cada elemento en un tipo de cadena y luego concatenarlo a su vez con ',', por lo que el resultado final es una cadena vacía, que es un valor del tipo original.
En este punto, la expresión se convierte en:
'' == 0
Los dos operandos se convierten en tipos de cadenas y tipos numéricos. Según la Figura 1, el tipo de cadena debe convertirse en tipos numéricos. Como se mencionó anteriormente, la cadena vacía se convierte en un número de 0. Por lo tanto, la expresión se convierte en:
0 == 0
Hasta ahora, los tipos de los dos operandos son finalmente los mismos, y el resultado obviamente es cierto.
A partir de este ejemplo, podemos ver que para dominar las reglas de la operación ==, además de recordar la Figura 1, también debemos recordar las reglas de los métodos ToString () y ValueOf () de esos objetos incorporados. Incluyendo objeto, matriz, fecha, número, cadena, booleano, etc.
8. Resumamos
La declaración anterior es muy confusa. Aquí resumiré las reglas de operación == expresadas en la Figura 1:
El resultado de UNDEFINADO == NULL es verdadero. El resultado de su comparación con todos los demás valores es falso.
Cuando un número de cadena ==, la cadena se convierte en un número.
Valor booleano == Cuando se usan otros tipos, el valor booleano se convierte en un número.
Cuando un objeto == numérico/cadena, el objeto se convierte en un tipo primitivo.
Finalmente, cambié la imagen para ser solo por entretenimiento :)
Ok, se acabó. Si cree que este artículo es útil para usted, me guste para que más personas puedan verlo.
Además, señale las falacias en el artículo.