Desestructuración en Javascript. Parte 2 (recetas y ejemplos)

07 Jul 2016

Introducción

En el anterior artículo revisamos la sintaxis, teoría y algunos ejemplos de esta nueva funcionalidad en ES6 que es la desestructuración.

Vamos a completar ahora la teoría con muchos ejemplos, o recetas, más complejos que iremos comentando según sea necesario. ¡Vamos a ello!

Combinando objetos y arrays

Como ya vimos, la desestructuración puede aplicarse sobre cualquier tipo de colección, ya sea un array, un objeto. Pero incluso podemos combinar ambos al mismo tiempo:

var { foo: x, bar: [ y, z ] } = { foo: 'Hello World', bar: [ 'Goodbye', 'Lenin' ] };
 
console.info( x, y, z ); // Hello World Goodbye Lenin

Valores por defecto

Podemos indicar valores por defecto para las variables del conjunto de entrada que no encuentren correspondencia en el conjunto de valores:

var [ foo = 'Hello World', bar = 'Goodbye Lenin' ] = [ 'OK', undefined ];
 
console.info( foo, bar ); // OK Goodbye Lenin

NOTA: En este caso, hay que recordar que ‘undefined’ dispara siempre el valor por defecto, no ocurriendo así con otros valores de tipo falsy:

var [ x = 'one', y = 'two', z = 'three' ] = [ null, 'OK', '' ];
console.info( x, y, z ); // null OK (cadena vacía)
 
var [ x = 'one', y = 'two', z = 'three' ] = [ NaN, 'OK', [] ];
console.info( x, y, z ); // NaN OK []

Los valores por defecto, como ocurre en los parámetros de las funciones, pueden ser una función:

var isRequired = ( name ) => { throw new Error( 'Missing parameter: ' + name ); }
 
var [ foo = isRequired( 'foo' ), bar = isRequired( 'bar' ) ] = [ 'OK' ];
 
// Error: Missing parameter: bar

Y también se pueden aplicar sobre el valor de una clave en un objeto:

var obj = {
    foo: 'Hello',
    bar: 'World'
};
 
var { foo, bar, foobar = 'Default' } = obj;
console.info( foo, bar, foobar ); // Hello World Default

IMPORTANTE: FIREFOX

Mientras se escribe este artículo, la versión actual de Firefox (la 47), no soporta los valores por defecto para objetos en determinados escenarios. Tomemos el siguiente ejemplo:

var books = [
    {
        title: "La vida del lazarillo de Tormes"
    }, {
        title: "The Never Ending Story",
        author: "Michael Ende"
    }, {
        title: "The Lords of the Rings",
        author: "J. R. R. Tolkien"
    }, {
        title: "Beowulf"
    }
];
 
for ( var { title = 'Unknown', author = 'Anonymous' } of books ) {
    console.log( title, author );
}

El código anterior reproduce una colección que recoge nombres y autores de libros. Cuando nuestra respuesta no incluye cualquiera de esas claves, podríamos utilizar las funcionalidades del lenguaje para que Javascript aplique un nombre por defecto a cualquiera de ellos.

En Chrome, la respuesta sería la esperada:

// La vida del lazarillo de Tormes Anonymous
// The Never Ending Story Michael Ende
// The Lords of the Rings J. R. R. Tolkien
// Beowulf Anonymous

Sin embargo, en Firefox obtenemos un error:

// SyntaxError: destructuring defaults aren't supported in this destructuring declaration

Para que la cosa funcione, tendríamos que reescribir el bucle for of de este modo:

for ( var book of books ) {
    ( { title = 'Unknown', author = 'Anonymous' } = book );
 
    console.info( title, author );
}

Iterando sobre determinados elementos

Volvamos a nuestra lista imaginaria de libros para comprobar cómo podemos recoger solo aquellas claves que necesitemos mientras iteramos por ella:

var books = [
    {
        title: "La vida del lazarillo de Tormes",
        author: "Anonymous",
        published: "1554"
    }, {
        title: "The NeverEnding Story",
        author: "Michael Ende",
        published: "1984"
    }, {
        title: "The Lord of the Rings",
        author: "J. R. R. Tolkien",
        published: "1954"
    }, {
        title: "Beowulf",
        author: "Anonymous",
        published: "900"
    }
];
 
for ( var { title } of books ) {
    console.info( title );
}

Extrayendo claves de un objeto en la línea de parámetros de una función

Otra de las posibilidades que ahora nos brindan las funciones en Javascript es poder aplicar la desestructuración directamente en los parámetros que recibe.

Volvemos a utilizar el array ‘books‘ anterior y generemos por cada libro un título amigable para URLs:

var parseBookTitle = function ( { title = 'Unknown' } ) {
    return title.replace( /\s+/g, '-' ).toLowerCase()
};
 
console.info( books.map( parseBookTitle ) );
// [ "la-vida-del-lazarillo-de-tormes", "the-neverending-story", "the-lord-of-the-rings", "beowulf" ]

O si preferimos la sintaxis de las Funciones Flecha, también funciona:

var parseBookTitle = ( { title } ) => title.replace( /\s+/g, '-' ).toLowerCase();

Parámetros de funciones completamente opcionales

En la misma línea del ejemplo anterior, podemos usar la desestructuración para crear Funciones Puras que puedan funcionar sin parámetros de entrada:

var random = ( { min = 1, max = 999 } = {} ) =>
    Math.floor( Math.random() * ( max - min ) ) + min;
 
console.info( random() ); // 845
console.info( random( { min: 10, max: 20 } ) ); // 16
console.info( random( { min: 10, max: 20 } ) ); // 17
console.info( random( { min: 10, max: 20 } ) ); // 13

Operador de arrastre

Del mismo modo que podemos utilizar el operador de acarreo/arrastre en funciones, puede aplicarse también durante la desestructuración:

var [ x, ...y ] = 'abc';
 
console.info( x, y ); // a ["b", "c"]

Este esquema se puede forzar aún más:

var [ x, ...[ y, z ] ] = [ 'a', 'b', 'c' ];
 
console.info( x, y, z ); // a b c

Con objetos también funciona:

var obj = {},
    foo;
 
[ foo, ...obj.rest ] = [ 'La', 'donna', 'e', 'mobile' ];
 
console.info( foo ); // La
console.info( obj.rest ); // ["donna", "e", "mobile"]

Desestructurando expresiones regulares

¿Necesitamos desestructurar las diferentes partes de una URL? No hay problema; es muy fácil:

var url = 'https://openlibra.com/es/book/learn-to-code-with-scratch';
 
var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec( url );
var [ , protocol, fullhost, fullpath ] = parsedURL;
 
console.info( protocol ); // https:
console.info( fullhost ); // openlibra.com

La idea aquí es utilizar el array de salida generado por la expresión regular y darle nombre a cada una de sus partes para una reutilización más clara.

O si preferimos montar un objeto que sirva de mapa con sus correspondientes parejas de clave/valor:

var parsedURLObj = {};
 
[ , parsedURLObj.protocol, parsedURLObj.fullhost, parsedURLObj.fullpath ] = parsedURL;
 
console.info( parsedURLObj );
// Object { protocol="https",  fullhost="openlibra.com",  fullpath="es/book/learn-to-code-with-scratch" }

Swap (o intercambio de valores entre variables)

Gracias a la desestructuración podemos intercambiar valores entre variables sin necesidad de declarar otra temporal intermedia:

var foo = 'Hello',
bar = 'World';
 
[ foo, bar ] = [ bar, foo ];
 
console.info( foo ); // World
console.info( bar ); // Hello

Este tipo de estructura es muy interesante cuando trabajamos con algoritmos. Tomemos por ejemplo el famoso algoritmo de Euclides para calcular el máximo común divisor (mcd); en Javascript tradicional, podríamos escribirlo de este modo:

function gcd ( a, b ) {
    if ( b > a ) {
        var temp = a;
        a = b;
        b = temp;
    }
 
    while ( true ) {
        a %= b;
        if ( a === 0 ) { return b; }
        b %= a;
        if ( b === 0 ) { return a; }
    }
}
 
console.info( gcd( 35, 75 ) ); // 5
console.info( gcd( 21, 81 ) ); // 3

NOTA: El código ha sido tomado de aquí, aunque he suprimido las dos primeras líneas (comprobaciones) para no hacer el fragmento más largo.

La clave está en el uso de una variable intermedia temp para intercambiar los valores de ‘a’ y ‘b’. Si utilizamos la desestructuración, el código se reduce del siguiente modo:

function gcd ( a, b ) {
    if ( b > a ) {
        [ b, a ] = [ a, b ];
    }
 
    while ( true ) {
        a %= b;
        if ( a === 0 ) { return b; }
        b %= a;
        if ( b === 0 ) { return a; }
    }
}
 
console.info( gcd( 35, 75 ) ); // 5
console.info( gcd( 21, 81 ) ); // 3

Desestructurando series infinitas

Próximamente trataré este tema en profundidad pero, como aperitivo, podemos utilizar la desestructuración para extraer porciones/segmentos dentro de una serie infinita. Esto en algoritmia es una poderosa herramienta que abre las puertas a la computación paralela y asíncrona.

Tomemos por ejemplo la siempre útil sucesión de Fibonacci, convirtiéndola en una función de tipo Generador. A continuación, extraigamos por desestructuración solo sus primeros elementos:

var fibonacci = function* () {
    var n1 = 1, n2 = 1;
 
    while ( true ) {
        var current = n2;
        n2 = n1;
        n1 = n1 + current;
 
        yield current;
    }
};
 
[ a, b, c, d, e, f ] = fibonacci();
 
console.info( a, b, c, d, e, f );
// 1 1 2 3 5 8

Por supuesto, gracias a la elisión, podemos omitir resultados:

[ , , , , , , a, b, c, d, e, f ] = fibonacci();
 
console.info( a, b, c, d, e, f );
// 13 21 34 55 89 144

Ya que estamos tratando el tema, podemos desestructurar el contenido del bucle ‘while‘ anterior. Perdemos legibilidad pero nos permitimos esa licencia como un ejercicio para la ocasión:

var fibonacci = function* () {
    var n1 = 1, n2 = 1;
 
    while ( true ) {
        var current = n2;
        [ n2, n1 ] = [ n1, ( n1 + current ) ]; // Don't Try This at Home
 
        yield current;
    }
};

NOTA: Recordad que no es posible utilizar la sintaxis de las Funciones Flechas cuando estamos trabajando con Generadores. No podríamos, por ejemplo, escribir algo como lo siguiente:

var fibonacci = *() => {
    // Uncaught SyntaxError: Unexpected token *
};

Conclusión

Con este segundo artículo completamos por el momento este repaso a la desestructuración en el nuevo estándar ES6. Hemos podido ver varios ejemplos donde esta estructura puede resultar útil, exprimiendo sus posibilidades y dando una suerte de recetas que pueden ayudarnos a comprenderlas mejor.

No quiero cerrar esta serie sin comentar cómo el uso y abuso de estas estructuras pueden también complicarnos la vida. Es posible que lleguemos a perdernos en un fragmento de código por culpa de una serie de instrucciones/asignaciones que no resultan evidentes. Si al final, por culpa de tanto corchete tenemos que abrir la consola del navegador y comenzar a poner puntos de ruptura para comprobar el valor de nuestras variables a cada línea, no habremos ganado nada.

La desestructuración es una buena herramienta que ha funcionado perfectamente en otros lenguajes, pero conviene no volvernos locos. Evitemos sacrificar la legibilidad de nuestros programas solo por el postureo de parecer modernos. Como siempre digo, lo más inteligente es escribir código pensando que el siguiente programador en leerlo puede ser un psicópata que sabe dónde vivimos…

Más:

Solo un marciano comentando!

  1. Edwin

    Excelente artículo : cada ejemplo ayuda para comprender el alcance y los diferentes matices del tema. La flexibilidad que proporciona Javascript, además de su capacidad como lenguaje de programación real, lo convierte en una delicia intelectual.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *