Seleccionar página

Todos sabemos que los objetos pueden contener otros objetos. 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 partida. Esta referencia circular no es un error o un problema, es una característica del lenguaje que podemos aprovechar, aunque debemos tenerla en cuenta a la hora de realizar algunas operaciones, ya que si no la tenemos en cuenta podemos provocar un problema.

Creando referencias circulares

Vamos a intentar explicar un poco más que es una referencia circular con un ejemplo muy sencillo:

var obj1 = {
    a: 1,
    b: 2
};
obj1.self = obj1;

En este código se puede ver una referencia circular simple, ya que el objeto hace referencia a sí mismo. El objeto obj1 tiene una propiedad self que hace referencia a obj1, es decir, a sí mismo. Seguramente no tiene mucha utilidad, pero el lenguaje acepta este tipo de referencias sin problemas.

Un caso más complejo, y en gran medida más habitual, es cuando la referencia circular se produce a través de otros objetos, es decir, tenemos un objeto que apunta en una de sus propiedades a otro objeto y este segundo hace referencia al objeto inicial.

Vamos a verlo con un ejemplo:

var parent = {
    sons: []
};
var child1 = {
    a: 1,
    b: true,
    c: 'hello',
    parent: parent
};
parent.sons.push(child1);
var child2 = {
    a: 2,
    b: false,
    c: 'bye',
    parent: parent
};
parent.sons.push(child2);
 

En este caso el objeto parent contiene una matriz con todos sus hijos (sons, y cada uno de los objetos child1 y child2 tienen una referencia a parent. En estas circunstancias, si recorremos en profundidad las propiedades, antes o después volveremos al objeto de partida, ya que existe una referencia circular.

Las referencias circulares son mucho más comunes de lo que podemos pensar a primera vista. En el DOM de los navegadores hay una gran cantidad de referencias circulares, ya que existen referencias entre unos elementos y otros.

Referencias circulares y funciones nativas

Para empezar vamos a ver qué ocurre cuando se hace un sencillo console.log() con un objeto con referencia circular. Realmente el resultado depende del navegador o del entorno en el que estamos trabajando: o bien permite ir navegando interactivamente de forma infinita por los elementos anidados o bien muestra algún mensaje del tipo parent: [Circular] para indicarnos esta característica.

Una situación muy habitual, y que causa muchas molestias, es utilizar JSON.stringify() con objetos con referencias circulares. Obtendremos un error del tipo TypeError: Converting circular structure to JSON. Esto es muy habitual al intentar serializar con JSON.stringify() un objeto del DOM, ya que -como hemos comentado- estos objetos tienen numerosas referencias circulares.

Aunque no es una solución completa al problema de JSON.stringify() con las referencias circulares, podemos resolver parte de este problema por medio de la función que podemos pasar como segundo parámetro a JSON.stringify(). Esa función es llamada por cada uno de los elementos de los objetos y si guardamos una referencia a los objetos ya procesados en una matriz podremos detectar la referencia circular y hacer algo específico en ese caso.

var result = JSON.stringify(parent, (function() {
    var stack = [];
    return function (key, value) {
        if (typeof value === 'object' && value !== null) {
            if (stack.indexOf(value) !== -1) {
                return undefined;
            }
            stack.push(value);
        }
        return value;
    };
})());

En este caso simplemente hemos devuelto undefined ya que JSON.stringify() ignora estos valores. También podríamos haber devuelto un, dar un aviso o lo que se nos ocurra. No es una solución perfecta, pero al menos no da un error y podremos obtener una representación aproximada del objeto.

Implementación en equal()

Desde hace algunas semanas venimos viendo con diferentes artículos cómo desarrollar una función universal que sea capaz de comparar todos los tipos de datos Javascript. Ahora vamos a ver cómo implementar una solución cuando nos encontramos con referencias circulares.

Hasta ahora, la versión que hemos desarrollado, no tiene en cuenta esta circunstancia, y en el caso de encontrar referencias circulares seguirá analizando hasta que se produzca un error del tipo RangeError: Maximum call stack size exceded. Al realizarse una llamada recursiva a la propia función equal() y existir referencias circulares en los objetos, las llamadas se van encadenando hasta que desbordan el máximo número de llamadas recursivas que son soportadas.

Para gestionar adecuadamente los objetos con referencias circulares utilizaremos una estrategia similar a la que hemos mostrado antes en el caso de JSON.stringify(), crear una matriz en la que almacenamos los objetos ya analizados y comprobaremos si el nuevo valor ya existe dentro de esa matriz. Si existe, es que ya lo hemos procesado y no seguiremos profundizando.

Pasaremos de un código de este tipo:

function equal(a, b, options) {
    var aValue, bValue, aKeys, bKeys, i,   // Define variables
        aDescriptor, bDescriptor,
        aType = typeof a,                  // Get value types
        bType = typeof b;
    options = options || {};               // Optional parameter

    // ...
}

A un código estructurado de esta forma:

function equal(a, b, options) {
    var aStack = [],                       // Stack array
        bStack = [];
    options = options || {};               // Optional parameter
    return (function check(a, b) {
        if (aStack.indexOf(a) > -1 &&      // Check if the object has been previously processed
            bStack.indexOf(b) > -1)
        {
            return OBJECT;
        }
        aStack.push(a);                    // Storage objects into stacks for recursive reference
        bStack.push(b);

        //...
    })(a, b);
}

Aunque puede parecer un poco enrevesado, lo que se hace es verdaderamente sencillo: dentro de la función equal() se ha creado una par de matrices donde vamos guardando referencias a los valores pasados como parámetros. También hemos creado una función check() que llamamos directamente y es la que realmente hace la comparación de forma recursiva. Si encontramos los valores en la matriz, es que ya hemos procesado esos valores y no debemos seguir llamando recursivamente a la función check().

Hemos puesto aquí solo un esquema de funcionamiento. Si quieres puedes consultar el código completo en GitHub y el juego de pruebas que incluye este caso.

Conclusiones

Los objetos con referencias recursivas pueden ser muy útiles para gestionar estructuras de datos anidadas, listas enlazadas, con referencias complejas, etc. No son un error y debemos entender que se pueden gestionar con un poco más de esfuerzo por nuestra parte.

En mecanismo planteado para gestionar las referencias circulares es verdaderamente sencillo y podemos implementarlo sin demasiada dificultad en nuestras funciones. Incluso si queremos utilizar JSON.stringify() podemos pasar una función que gestione esta circunstancia de forma adecuada. No tenemos excusa para no utilizar esta característica de Javascript por miedo a encontrarnos con una llamada recursiva que exceda la pila de llamadas. Simplemente tenemos que ser capaces de identificar esta referencia circular y gestionarla.

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.