Seleccionar página

Aunque pueda parecer que comparar dos valores es algo sencillo y común, lo cierto es que comparar en Javascript conlleva algunas dificultades que debemos conocer y manejar para evitar problemas en nuestro código. Una de estas características singulares es la diferencia entre la comparación estricta y no estricta, es decir, la diferencia entre == (con sólo dos signos de igualdad) y === (con sólo tres signos de igualdad).

Antes de avanzar tenemos que recordar que Javascript es un lenguaje con un tipado débil, es decir, los valores tienen un tipo, pero este puede ser cambiado de forma implícita en cualquier momento. De esta forma, si escribimos algo como 1 + "0" obtendremos una cadena con el valor "10". La clave para comprender la diferencia entre las dos formas de hacer las comparaciones == (con sólo dos signos de igualdad) y === (con sólo tres signos de igualdad) es la conversión de tipos.

Se suele considerar que es conveniente utilizar la comparación estricta, pero veamos con más detenimiento como funcionan y que ventajas e inconvenientes tenemos en cada caso.

Comparación no estricta o con conversión de tipo

La comparación con == o con !== es denominada como “comparación no estricta”, ya que realiza conversiones de tipo. Si los dos datos que estamos comparando no son del mismo tipo, se realizan una serie de conversiones de tipo a fin de encontrar una coincidencia con alguna de las combinaciones que internamente evalua.

El problema es que estas conversiones no son siempre muy evidentes y nos pueden desconcertar. Por ejemplo, las siguientes expresiones son todas verdaderas, aunque pueda parecer que no debería ser así:

console.assert(      1 == true         );
console.assert(    "1" == true         );
console.assert(    [1] == true         );
console.assert(     "" == false        );
console.assert(    "0" == false        );
console.assert(      0 == false        );
console.assert(    [0] == false        );
console.assert(    [ ] == false        );
console.assert(   [[]] == false        );
console.assert(      0 == ""           );
console.assert(      0 == "0"          );
console.assert(      1 == "1"          );
console.assert(    [ ] == 0            );
console.assert(   [[]] == 0            );
console.assert(    [0] == 0            );
console.assert(    [ ] == ""           );
console.assert(   [[]] == ""           );
console.assert(   null == undefined    );
console.assert(  ',,,' == new Array(4) );

Nota: estamos utilizando console.assert(), método que explicamos al describir el sistema de pruebas minimalista en Javascript que vamos a utilizar.

Seguramente algo como 1 == true podemos aceptarlo, ya que ambos son valores que se suelen denominar como truthy (que se consideran como vedaderas), y las usamos muy habitualmente, por ejemplo, dentro de instrucciones if (). Aunque no nos estemos dando cuenta, cuando consideramos que 1 es verdadero y 0 es falso en un if (), estamos aprovechando una conversión implícita de tipo.

Seguramente nos sintamos más confundidos a la hora de comparar cadenas y números, ya que 1 == "1" no parece que deba ser verdadero, pero esta conversión de tipos es más o menos previsible.

Lo que es muy difícil de comprender es que algo como [[]] sea igual a 0 o ',,,' sea igual que new Array(4). Estos caprichos de la conversión de tipos en Javascript ha dado lugar una buena colección de chistes, presentaciones y divertimentos, además de problemas graves en más de un programa en producción.

En muchos casos los analizadores de código dan un aviso o un error cuando encuentran un == advirtiendo que quizás hemos querido poner === y por un descuido nos hemos saltado un signo de igual. Pero en ocasiones utilizar la conversión de tipo puede ser útil y es algo conveniente, pero es cierto que un pequeño descuido puede hacer cambiar sustancialmente el comportamiento de nuestro programa.

Comparación estricta o sin conversión de tipos

En general se considera que la comparación estricta con === o !== es más conveniente y segura. Este tipo de comparación no realiza ningún tipo de conversión de tipo y nos asegura que coinciden tanto el valor como el tipo de los dos elementos que estamos comparando. Si que comparamos datos de tipos diferentes, siempre se producirá un resultado negativo. En ocasiones se dice que este tipo de comparaciones comprueba tipo y valor, aunque realmente lo que hace es no realizar ningún tipo de conversión.

Todos los casos que vimos antes ahora devuelven false, por lo que para que console.assert() nos acepte la instrucción que queremos comprobar hemos utilizado la negación estricta !==.

console.assert(      1 !== true         );
console.assert(    "1" !== true         );
console.assert(    [1] !== true         );
console.assert(     "" !== false        );
console.assert(    "0" !== false        );
console.assert(      0 !== false        );
console.assert(    [0] !== false        );
console.assert(    [ ] !== false        );
console.assert(   [[]] !== false        );
console.assert(      0 !== ""           );
console.assert(      0 !== "0"          );
console.assert(      1 !== "1"          );
console.assert(    [ ] !== 0            );
console.assert(   [[]] !== 0            );
console.assert(    [0] !== 0            );
console.assert(    [ ] !== ""           );
console.assert(   [[]] !== ""           );
console.assert(   null !== undefined    );
console.assert(  ',,,' !== new Array(4) );

Función de comparación que permita ambos casos

Como hemos comentado, estamos creando una función universal que permita la comparación de cualquier tipo de datos y tenemos que tener en cuenta estos dos tipos de comparaciones. Aunque por defecto useremos una comparación estricta, es cierto que en ocasiones nos puede interesar hacer una comparación con conversión de tipos, pero siempre dejando claro que es algo intencionado y no involuntario, como puede ser el olvidarse de un = escribiendo == en vez de === por un sencillo descuido.

La idea es que nuestra función realize una la comparación estricta por defecto de esta forma:

function equal(a, b) {
    if (a === b) {
        return true;
    }
    return false;
}

Segunda versión de esqueal.js en GitHub y su juego de pruebas.

Para incorporar la posibilidad de hacer una comparación no estricta tenemos que incluir un parámetro opcional (que como luego será ampliado, lo hemos definido como un objeto con varias opciones).

Para poder dar una respuesta precisa a la hora de hacer la comparación hemos incluido diferentes valores de retorno: 0 cuando no es igual, 1 cuando es igual el valor y 2 cuando coinciden valor y tipo.

function equal(a, b, options) {
    options = options || {};            // Optional parameter
    if (a === b) {                      // Strict comparison
        return equal.VALUE_AND_TYPE;    // Equal value and type
    }
    if (options.nonStrict && a == b) {  // Non strict comparison (optional)
        return equal.VALUE;             // Equal value (different type)
    }
    return equal.NOT_EQUAL;             // Not equal
}
equal.NOT_EQUAL           = 0;
equal.VALUE               = 1;
equal.VALUE_AND_TYPE      = 2;

Tercera versión de esqueal.js en GitHub y sus pruebas.

Se ha incluido los valores equal.NOT_EQUAL, equal.VALUE y equal.VALUE_AND_TYPE como propiedades de la función equal(). Las funciones son objetos y aceptan propiedades, por lo que son una buena opción para almacenar los valores utilizados por esa misma función como retorno. No hemos utilizado const ya que hemos decidido que el programa debe poder funcionar con cualquier navegador o versión de Node que soporte EcmaScript 5.1 y esta const una instrucción de ES6.

Aunque la funcionalidad implementada sigue siendo muy limitada, muestra algunos avances y permite comparar de forma estricta y no estricta sin posibilidad de equívoco, devolviendo valores consistentes y precisos.

console.assert( !equal( true, 1 ) );
console.assert(  equal( true, 1, {nonStrict: true}  ) );
console.assert(  equal( true, 1, {nonStrict: true}  ) === equal.VALUE );
console.assert(  equal( true, 1, {nonStrict: true}  ) !== equal.VALUE_AND_TYPE );

Conclusiones

No debemos considerar la comparación no estricta como el demonio, es útil, pero debemos estar seguros que es lo que queríamos hacer y no escribir por descuido == cuando queríamos escribir ===.

Nuestra función de comparación universal de valores en Javascript todavía carece de una funcionalidad realmente interesante, pero va tomando cuerpo y en este recorrido nos permite repasar algunas características interesantes del lenguaje. De momento ya incluye ambos tipos de comparación, estricta y no estricta. En los próximos capítulos avanzaremos de forma significativa, pero, si no puedes esperar, descarga la versión final de esqueal.js con npm.

Novedades

Explorando ArrayBuffer, DataView y matrices con tipo

Hasta hace relativamente poco en Javascript era complicado gestionar datos binarios. ArrayBuffer, DataView y las matrices con tipo (Typed Array) ponen a nuestra disposición un conjunto bastante completo de herramientas para manejar tipos binarios sin problemas. Vamos a ver cómo funcionan…

Objetos Map y Set

Los objetos Map y Set nos pueden ser de gran ayuda para gestionar conjuntos de datos, pudiendo simplificar nuestros programas en muchas circunstancias. Es interesante que sepamos cómo se utilizan y que pequeños secretos esconden. Vamos a revisarlos…

Referencia circular en objetos

Todos sabemos que los objetos pueden contener otros objetos, pero de lo que quizás no somos conscientes es que con mucha facilidad podemos crear una referencia circular, es decir, que si recorremos las propiedades del objeto y vamos profundizando, llegamos de nuevo al objeto inicial. Debemos tener en en cuenta esta circunstancia a la hora de realizar algunas operaciones o tendremos problema. Veamos cómo…

Características de las propiedades de los objetos

Existen varios tipos de propiedades que se comportan de forma diferente. Tenemos que tener en cuenta es la diferencia entre propiedades enumerables y no enumerables, propias y heredadas, de sólo lectura o no configurables, sin olvidar alguna que otra convención para definir propiedades como privadas. Veamos cómo trabajar con los distintos tipos de propiedades de un objeto.

breves

Descrubir algunas características de console

En el día a día nos encontramos muy a menudo utilizando console. Es una navaja multiusos que nos facilita la vida a la hora de depurar nuestro código. La mayoría de nosotros ha utilizado console.log(), pero tiene otras muchas funcionalidades.

Matrices dispersas o sparse arrays en JS

Una característica que puede producir algunos problemas, si no lo tenemos en cuenta, es la posibilidad de tener matrices con huecos, es decir, con algunos de sus elementos sin definir. Es lo que se suele denominar una matriz dispersa o sparse array. Veamos cómo trabajar con esta características de las matrices.

Algunos operadores de bits usados con asiduidad

Cada día más a menudo podemos encontrar operadores binarios utilizados como formas abreviadas de algunas operaciones que de otra forma sería algo menos compactas y, quizás, más comprensibles. Veamos algunos casos en detalle.

Cómo diferenciar arrow function de function

En un reciente artículo Javier Vélez Reyes hace patente las principales diferencias entre las funciones tradicionales y las funciones flecha, ya que ambos modelos no son equivalentes e intercambiables. Veamos cómo es posible saber si una función ha sido construida por medio de la instrucción function o como una arrow function.