Libro "{No sabes JS} Tipos y construcciones gramaticales"

imagen Independientemente de la experiencia de programación de JavaScript que tenga, lo más probable es que no comprenda completamente el lenguaje. Esta guía concisa explora los tipos más profundamente que todos los libros existentes: aprenderá cómo funcionan los tipos, los problemas de su conversión y aprenderá a usar nuevas funciones.

Al igual que otros libros de la serie "You Don't Know JS" , muestra aspectos no triviales del lenguaje de los que los programadores de JavaScript prefieren mantenerse alejados (o suponen que no existen). Armado con este conocimiento, logrará un verdadero dominio de JavaScript.

Extracto La igualdad es estricta y no estricta.


La igualdad no estricta es verificada por el operador ==, y la igualdad estricta por el operador ===. Ambos operadores se utilizan para comparar dos valores de "igualdad", pero la elección de la forma (estricta / no estricta) conduce a diferencias muy importantes en el comportamiento, especialmente en la forma en que se toma la decisión sobre la igualdad.

Hay una idea errónea común sobre estos dos operadores: "== verifica la igualdad de valor y === verifica la igualdad de ambos valores y tipos". Suena razonable
pero inexacto Innumerables libros y blogs de JavaScript de buena reputación dicen exactamente eso, pero desafortunadamente todos están equivocados.

La descripción correcta es: "== permite la conversión de tipos cuando se verifica la igualdad, y === prohíbe la conversión de tipos".

Rendimiento de verificación de igualdad


Detente y piensa en cómo la primera explicación (imprecisa) difiere de la segunda (exacta).
En la primera explicación, parece obvio que el operador === hace más trabajo que == porque también necesita verificar el tipo.

En la segunda explicación, el operador == hace más trabajo, porque con diferentes tipos tiene que pasar por la conversión de tipos.

No caigas en la trampa en la que muchos caen. No piense que esto afectará de alguna manera la velocidad del programa, y ​​== será significativamente más lento ===. Aunque la conversión lleva algo de tiempo, lleva una cuestión de microsegundos (sí, millonésimas de segundo).

Si está comparando dos valores del mismo tipo, == y === use el mismo algoritmo, por lo que si no tiene en cuenta las pequeñas diferencias en la implementación del motor, deben realizar uno
y el mismo trabajo

Si está comparando dos valores de diferentes tipos, el rendimiento no es un factor importante. Tiene que preguntarse algo más: si estoy comparando dos valores, ¿quiero que ocurra la conversión de tipos o no?

Si necesita una conversión, use igualdad no estricta ==, y si la conversión no es deseable, use igualdad estricta ===.

Ambos operadores, == y ===, verifican los tipos de sus operandos. La diferencia es cómo responden a la falta de coincidencia de tipos.

Verificación de igualdad abstracta


El comportamiento del operador == se define en la sección 11.9.3 de la especificación ES5 ("Algoritmo de verificación de igualdad abstracta"). Aquí hay un algoritmo detallado pero simple, con una lista explícita de todas las combinaciones posibles de tipos y métodos de conversión de tipos (si es necesario) que deben aplicarse en cada combinación.

Cuando alguien condena la conversión de tipo (implícita) como demasiado compleja y contiene demasiados defectos para un uso práctico útil, condena las reglas de la "verificación de igualdad abstracta". Por lo general, se dice que este mecanismo es demasiado complicado y poco natural para el estudio y uso prácticos, y que crea errores en los programas JS en lugar de simplificar la lectura del código.

Creo que esta es una suposición errónea, porque ustedes, lectores, son desarrolladores competentes que escriben algoritmos, es decir, código (y también lo leen y entienden), todo el día. Por esta razón, trataré de explicar la "prueba de igualdad abstracta" en palabras simples. Sin embargo, también recomiendo leer la sección 11.9.3 de la especificación ES5. Creo que te sorprenderá lo lógico que está todo allí.

De hecho, la primera sección (11.9.3.1) establece que si dos valores comparados son del mismo tipo, se comparan de una manera simple y natural. Por ejemplo, 42 es solo 42, y la cadena "abc" es solo "abc".

Algunas excepciones menores a tener en cuenta:

  • El valor de NaN nunca es igual a sí mismo (ver capítulo 2).
  • +0 y -0 son iguales entre sí (ver capítulo 2).

La última sección en la sección 11.9.3.1 está dedicada a una prueba rigurosa de == igualdad con objetos (incluidas funciones y matrices). Dos de estos valores son iguales solo si ambos se refieren exactamente al mismo valor . No se realiza conversión de tipo.

Una verificación de igualdad estricta === se define de manera idéntica a 11.9.3.1, incluida la provisión para dos valores de objeto. Este hecho es muy poco conocido, pero == y === se comportan completamente idénticos al comparar dos objetos.

El resto del algoritmo en 11.9.3 indica que la igualdad suelta == se puede usar para comparar dos tipos diferentes de valores, uno o ambos requerirán
conversión implícita Como resultado de la conversión, las designaciones se convierten a un tipo, después de lo cual se pueden comparar directamente para la igualdad por identidad simple
valores

¡La operación de un control débil de la desigualdad! = Se determina exactamente como uno esperaría; de hecho, la operación == está completamente implementada, seguida del cálculo
negación de resultado. ¡Lo mismo se aplica a la operación de verificar estrictamente la desigualdad! ==.

Comparación: cadenas y números


Para demostrar la conversión de ==, primero cree ejemplos de cadenas y números, que se realizó anteriormente en este capítulo:

var a = 42; var b = "42"; a === b; // false a == b; // true 

Como se esperaba, la comprobación a === b falla porque la conversión no está permitida y los valores 42 y "42" son diferentes.

Sin embargo, en la segunda comparación a == b, se utiliza la igualdad no estricta; Esto significa que si los tipos son diferentes, el algoritmo de comparación realizará una conversión implícita de uno
o ambos

Pero, ¿qué tipo de conversión se realiza aquí? ¿El valor a, es decir, 42, se convertirá en una cadena, o el valor b "42" se convertirá en un número? La especificación ES5 en las secciones 11.9.3.4–5 dice:

  1. Si Tipo (x) es de tipo Número y Tipo (y) es de tipo Cadena, devuelve el resultado de la comparación x == ToNumber (y).
  2. Si Tipo (x) es de tipo Cadena y Tipo (y) es de tipo Número, devuelva el resultado de la comparación ToNumber (x) == y.

En la especificación, se usan nombres formales de los tipos de Número y Cadena, mientras que en el libro para los tipos primitivos, generalmente se usa el número de notación y la cadena. No confunda el caso del símbolo de número en la especificación con la función incorporada de número (). Para nuestros propósitos, el caso de los caracteres en el nombre del tipo no juega un papel, significan lo mismo.

La especificación dice que el valor "42" se convierte en un número para comparación. Acerca de cómo se realiza la conversión, ya se describió anteriormente, y específicamente cuando se describe la operación abstracta ToNumber. En este caso, es bastante obvio
que los dos valores resultantes de 42 son iguales.

Comparación: cualquier cosa con booleanos


Una de las trampas más peligrosas en la conversión implícita de tipo == se encuentra al intentar comparar directamente los valores con verdadero o falso.

Un ejemplo:

 var a = "42"; var b = true; a == b; // false 

Espera, ¿qué está pasando aquí? Sabemos que "42" es el verdadero significado (ver más arriba en este capítulo). ¿Cómo resulta que compararlo con verdadero con la declaración de igualdad estricta ==
no da verdad?

La razón es simple y engañosamente astuta al mismo tiempo. Es fácil de entender mal, muchos desarrolladores de JS no hacen el esfuerzo de entenderlo completamente.

Una vez más citamos la especificación, secciones 11.9.3.6–7:

  1. Si Tipo (x) es de tipo booleano, devuelve el resultado de la comparación ToNumber (x) == y.
  2. Si Tipo (y) es de tipo booleano, devuelve el resultado de la comparación x == ToNumber (y).

Veamos que hay aquí. Primer paso:

 var x = true; var y = "42"; x == y; // false 

El tipo (x) realmente pertenece al tipo booleano, por lo que se realiza la operación ToNumber (x), que convierte verdadero a 1. Ahora se calcula la condición 1 == "42". Los tipos siguen siendo diferentes, por lo tanto (casi recursivamente) el algoritmo se repite; como en el caso anterior, "42" se convierte a 42, y la condición 1 == 42 es obviamente falsa.

Si intercambia operandos, el resultado seguirá siendo el mismo:

 var x = "42"; var y = false; x == y; // false 

Esta vez, Tipo (y) es de tipo booleano, por lo que ToNumber (y) da 0. La condición "42" == 0 se convierte recursivamente en 42 == 0, que, por supuesto, es falsa.

En otras palabras, el valor "42" no es == verdadero ni == falso. A primera vista, esta afirmación parece completamente impensable. ¿Cómo puede el significado no ser ni verdadero ni falso?

Pero este es el problema! Estás haciendo la pregunta equivocada. Aunque en realidad no es tu culpa, es el cerebro el que te está engañando.

El valor "42" es realmente cierto, pero la construcción "42" == verdadero no realiza una prueba booleana / transformada, lo que sea que diga su cerebro. "42" no se convierte a booleano (verdadero); en cambio, verdadero se convierte en 1, y luego "42" se convierte en 42.

Te guste o no, ToBoolean no se usa en absoluto aquí, por lo que la verdad o falsedad de "42" no es importante para la operación ==. Es importante comprender cómo se comporta el algoritmo de comparación == en todas las diferentes combinaciones de tipos. Si el valor booleano está en un lado, siempre se convierte primero en un número.

Si esto te parece extraño, no estás solo. Personalmente, recomiendo nunca, nunca, bajo ninguna circunstancia, usar == verdadero o == falso. Nunca

Pero recuerda que solo estoy hablando de == aquí. Las construcciones === verdadero y === falso no permiten la conversión de tipos, por lo que están protegidas de la conversión oculta ToNumber.

Un ejemplo:

 var a = "42"; //  (  !): if (a == true) { // .. } //   (  !): if (a === true) { // .. } //   ( ): if (a) { // .. } //  ( ): if (!!a) { // .. } //   ( ): if (Boolean( a )) { // .. } 

Si evita == verdadero o == falso (igualdad suelta con booleano) en su código, nunca tendrá que preocuparse por esta trampa de verdad / falsedad.

Comparación: nulo con indefinido


Otro ejemplo de conversión implícita ocurre cuando usa la igualdad lax == entre valores nulos e indefinidos. Nuevamente, citaré la especificación ES5,
secciones 11.9.3.2–3:

  1. Si x contiene nulo e y contiene indefinido, devuelve verdadero.
  2. Si x contiene indefinido e y contiene nulo, devuelve verdadero.

Nulo e indefinido en comparación con el operador no estricto == son iguales entre sí (es decir, se convierten entre sí) y no hay otros valores en todo el idioma.

Para nosotros, esto significa que nulo e indefinido puede considerarse indistinguible para fines de comparación si utiliza el operador de prueba de igualdad no estricto ==, que permite su conversión implícita mutua:

 var a = null; var b; a == b; // true a == null; // true b == null; // true a == false; // false b == false; // false a == ""; // false b == ""; // false a == 0; // false b == 0; // false 

La conversión entre nulo e indefinido es segura y predecible, y ningún otro valor puede dar falsos positivos para tal verificación. Recomiendo usar esta conversión para que nulo e indefinido no difieran en el programa y se interpreten como un valor único.

Un ejemplo:

 var a = doSomething(); if (a == null) { // .. } 

La comprobación nula a == solo pasa si doSomething () devuelve nulo o indefinido y falla por cualquier otro valor (incluyendo 0, falso y "").

¡La forma explícita de esta verificación, que prohíbe cualquier tipo de conversiones de este tipo, parece (en mi opinión) mucho más fea y puede funcionar un poco menos eficientemente!

 var a = doSomething(); if (a === undefined || a === null) { // .. } 

Creo que la forma a == nulo es otro ejemplo de una situación en la que una conversión implícita facilita la lectura del código, pero lo hace de manera confiable y segura.

Comparación: objetos y no objetos


Si se compara un objeto / función / matriz con una primitiva escalar simple (cadena, número o booleano), la especificación ES5 dice lo siguiente (sección 11.9.3.8–9):

  1. Si Tipo (x) es de tipo Cadena o Número, y Tipo (y) es de tipo Objeto, devuelve el resultado de la comparación x == ToPrimitive (y).
  2. Si Tipo (x) es de tipo Objeto y Tipo (y) es de tipo Cadena o Número, devuelve el resultado de la comparación ToPrimitive (x) == y.

Es posible que haya notado que en estas secciones de la especificación solo se mencionan String y Number, pero no Boolean. El hecho es que, como se mencionó anteriormente, las secciones 11.9.3.6–7 aseguran que cualquier operando booleano se represente primero como Número.

Un ejemplo:

 var a = 42; var b = [ 42 ]; a == b; // true 

Para el valor [42], se llama a la operación abstracta ToPrimitive (consulte "Operaciones abstractas"), que da el resultado "42". A partir de este momento, la condición simple "42" == 42 permanece, que, como ya hemos descubierto, se convierte en 42 == 42, de modo que ayb son iguales hasta la conversión de tipos.

Como era de esperar, todas las características de la operación abstracta ToPrimitive discutida anteriormente en este capítulo ((toString (), valueOf ()) también son aplicables en este caso. Esto puede ser muy útil si tiene una estructura de datos compleja y desea defina un método especializado valueOf () para él, que deberá proporcionar un valor simple con el propósito de verificar la igualdad.

El Capítulo 3 examinó el "desempaquetado" de un contenedor de objetos alrededor de un valor primitivo (como en una nueva Cadena ("abc"), por ejemplo), lo que resulta en el retorno de la primitiva subyacente
valor ("abc"). Este comportamiento está relacionado con la transformación ToPrimitive en el algoritmo ==:

 var a = "abc"; var b = Object( a ); //  ,  `new String( a )` a === b; // false a == b; // true 

a == b da verdadero porque b es convertido (o "desempaquetado") por la operación ToPrimitive al valor primitivo escalar simple base "abc", que coincide con el valor de a.

Hay algunos valores para los que esto no es así debido a otras reglas primordiales en el algoritmo ==. Un ejemplo:

 var a = null; var b = Object( a ); //  ,  `Object()` a == b; // false var c = undefined; var d = Object( c ); //  ,  `Object()` c == d; // false var e = NaN; var f = Object( e ); //  ,  `new Number( e )` e == f; // false 

Los valores nulo e indefinido no se pueden empaquetar (no tienen un contenedor de objetos equivalente), por lo que Object (nulo) no es fundamentalmente diferente de Object (): ambas llamadas crean el habitual
ny objeto

NaN se puede empaquetar en el contenedor de objetos Number equivalente, pero cuando == causa el desempaquetado, la comparación NaN == NaN falla porque el valor de NaN nunca es igual a sí mismo (vea el capítulo 2).

»Se puede encontrar más información sobre el libro en el sitio web del editor
» Contenidos
» Extracto

Cupón de 25% de descuento para vendedores ambulantes - JavaScript

Tras el pago de la versión en papel del libro, se envía un libro electrónico por correo electrónico.

Source: https://habr.com/ru/post/456730/


All Articles