Seleccionar página
Muchos elementos de Javascript son objetos. Las fechas, matrices y funciones son objetos. Podemos crear nuestros propios objetos por medio de expresiones literales ({}), funciones constructoras y new, con Object.create() y desde ES6 a partir de class. Los objetos están por todas partes en Javascript, pero nos encontramos que comparar objetos conlleva bastantes dificultades, ya que el lenguaje como tal no nos ofrece un mecanismo para comparar si dos objetos tiene las mismas propiedades.

Como continuación a nuestra serie de artículos donde vamos describiendo diferentes características de Javascript con la excusa de implementar una función universal que permita comparar cualquier tipo de datos, vamos a analizar la comparación de objetos en Javascript.

Comparación natural de objetos

De forma directa, cuando comparamos dos variables que contiene objetos, no se comparan los valores de las propiedades del objeto, se comparan sus referencias al objeto. Esto cuesta un poco de comprender: dos variables son iguales con === si apuntan exactamente al mismo objeto, no a dos objetos con iguales valores.

var obj1 = {a: 1, b: 2};
var obj2 = {a: 1, b: 2};
var obj3 = obj1;
console.assert( obj1 !== obj2 );
console.assert( obj1 === obj3 );

Cuando escribimos obj3 = obj1 lo que estamos haciendo es al asignar el objeto en otra variable, pero no se está creando un nuevo objeto, se está almacenando una referencia del objeto contenido en obj1 dentro de la variable obj3. El objeto sigue siendo el mismo, aunque ahora tiene dos variables que hacen referencia al mismo. De esta forma, si modificamos algo en obj1 ese cambio también será accesible desde obj3. De esta forma obj1 === obj3 es true ya que ambas variables apuntan exactamente al mismo objeto.

Esto complica bastante la vida del programador, ya que copiar y comparar objetos se vuelve una tarea bastante complicada. Objetos, matrices, fechas son todos objetos en Javascript y por lo tanto comparar este tipo de valores requiere de algún tipo de estrategia que podríamos considerar como no natural.

En el caso de las fechas y los objetos wrapper sobre los tipos nativos ya hemos explicado en nuestro último artículo cómo realizar su comparación sobre la base de valueOf(). Ahora nos centraremos en la forma de comparar los valores de los objetos por medio de sus propiedades.

Existen bastantes estrategias para realizar comparaciones de objetos y matrices en base a su contenido, desde utilizar JSON.stringify() para hacer una comparación de cadenas, hasta realizar recorridos por los elementos del objeto o matriz para comprobar si los valores son exactamente los mismos. Vamos a darles un repaso.

JSON.stringify() para la comparar de objeto

Lo cierto es que JSON.stringify() funciona en algunos casos y es un método que podemos ver recomendado en bastantes referencias en Internet, pero en la práctica tiene algunos problemas importantes y no es una segura y rápida forma de resolver el problema. Básicamente esta estrategia consiste en convertir los dos objetos a cadena y compararlas.

var obj1 = {a: 1, b: 2};
var obj2 = {a: 1, b: 2};
console.assert( JSON.stringify(obj1) === JSON.stringify(obj2) );

var arr1 = [1,2,3,4,5];
var arr2 = [1,2,3,4,5];
console.assert( JSON.stringify(arr2) === JSON.stringify(arr2) );

Los principales problemas de utilizar JSON.stringify() para comparar objetos son:

  • Las propiedades de los objetos no se muestra en orden alfabético, por lo que se pueden producir diferencias por el orden en el que han sido creadas.
  • Solo se incluyen las propiedades enumerables del objeto, lo cual puede ser algo limitado en algunos casos.
  • Podemos encontrarnos con objetos o tipos que pueden llegar a ser problemáticos en esta comparación en base a cadenas.
  • No funciona con objetos con referencias circulares, es decir, que dentro de un objeto se haga referencia a si mismo.
  • Es bastante lento, ya que además de serializar los dos objetos, se realizará una comparación completa de una cadena, que puede llegar a ser bastante larga

En este sencillo caso, el resultado no es el que podríamos esperar:

var obj1 = {a: 1, b: 2};
var obj2 = {b: 2, a: 1};
console.assert( JSON.stringify(obj1) === JSON.stringify(obj2) );

Simplemente cambiando el orden en el que se han creado las propiedades obtenemos cadenas diferentes (al menos en muchas implementaciones de Javascript), por lo que en la práctica esta no es una estrategia consistente y segura para comparar objetos.

Comparar analizando las propiedades del objeto

La otra estrategia para comparar el contenido de dos objetos es analizar las propiedades del objeto uno a uno y compararlas. De momento vamos a comparar sólo las propiedades propias enumerables tal y como las devuelve Object.keys(), más adelante nos cuestionaremos si estas son las únicas propiedades relevantes, pero para empezar es suficiente.

var i;
var obj1 = {a: 1, b: 2, c: true};
var obj2 = {c: true, b: 2, a: 1};
for (i in obj1) {
    console.assert(obj1[i] === obj2[i]);
}
for (i in obj2) {
    console.assert(obj1[i] === obj2[i]);
}

Para evitar este doble bucle, que necesitamos para estar seguros que no hay más propiedades en un objeto que en otro, podemos utilizar una aproximación similar, pero algo más sencilla, comparando el número de propiedades, el nombre de las mismas y sus valores.

function compareObj(a, b) {
    var aKeys = Object.keys(a).sort();
    var bKeys = Object.keys(b).sort();
    if (aKeys.length !== bKeys.length) {
        return false;
    }
    if (aKeys.join('') !== bKeys.join('')) {
        return false;
    }
    for (var i = 0; i < aKeys.length; i++) {
        if ( a[aKeys[i]]  !== b[bKeys[i]]) {
            return false;
        }
    }
    return true;
}

var obj1 = {a: 1, b: 2, c: true};
var obj2 = {c: true, b: 2, a: 1};
var obj3 = {c: false, b: 2, a: 1};

console.assert(  compareObj(obj1, obj2)  );
console.assert( !compareObj(obj1, obj3)  );

Aunque esta implementación compara correctamente los objetos, sólo hace la comparación de valores a primer nivel, es decir, si hay un objeto que contiene otro objeto, o una matriz que obtiene un objeto u otra matriz, no compararemos en profundidad el contenido.

Comparación de objetos en profundidad en equal()

Para solventar el problema de la comparación en profundidad lo que tenemos que hacer es llamar recursivamente a la función de comparación y comprobar las propiedades de cada objeto anidado. Para ello vamos implementar esta comparación recursiva en la función equal que venimos implementando en esta serie de artículos incorporando estas líneas a nuestra función.

if (aType === 'object') {
    aKeys = Object.keys(a).sort();                  // Get enumerate properties
    bKeys = Object.keys(b).sort();
    if (aKeys.length !== bKeys.length) {            // Check number of properties
        return equal.NOT_EQUAL;
    }
    if (aKeys.join('') !== bKeys.join('')) {        // Check name of properties
        return equal.NOT_EQUAL;
    }
    for (i = 0; i < aKeys.length; i++) {            // Check each property value (recursive call)
        if (!equal(a[aKeys[i]], b[bKeys[i]], options)) {
            return equal.NOT_EQUAL;
        }
    }
    if (a.constructor === b.constructor) {          // It's the same constructor and as result is the same type
        return equal.PROPERTIES_AND_TYPE;
    }
    if (options.nonStrict) {                        // Non strict comparison (optional)
        return equal.PROPERTIES;
    }
    return equal.NOT_EQUAL;                         // Not equal
}

Puedes consultar el código completo de esta versión de esequal.js en GitHub y sus pruebas.

Como en la función equal() ya comparamos cualquier otro tipo de dato, con la llamada recursiva a equal() podemos comparar sin problemas el contenido de los objetos, aunque estos contentan en sus propiedades valores con tipos primitivos o cualquier tipo de objeto.

Conclusiones

Parece sencillo comparar en Javascript, pero como estamos viendo, comparar los valores es una operación bastante compleja cuando nos enfrentamos a los objetos. La comparación natural de objetos sólo es cierta si estamos comparando exactamente la referencia al mismo objeto, pero no podemos comparar los contenidos del objeto sin entrar a analizar propiedad por propiedad.

Aunque nuestra función equal() parece ya bastante completa, lo cierto es que es muy mejorable, lo cual vamos a abordar en los próximos artículos y de esta forma nos adentraremos aún más en el mundo de los objetos en Javascript, profundizando sobre algunas de sus características menos comentadas, pero importantes. Si no puedes esperar, descarga la versión final de esqueal.js con npm.

Novedades

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)

En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.

En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.

Observables en Javascript con Proxies

Observables en Javascript con Proxies

En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

Aplicaciones JAMStack, SEO friendly y escalables con NextJS

En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera

En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.

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.

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.