Desestructuración en ES6. Parte III: Los operadores «esparcir» y «recolectar»

via GIPHY

La sintáxis ES6 para desestructurar arrays y objetos no es muy popular en la comunidad Javascript. Si has seguido esta serie hasta aquí, es posible que se te haya hecho cuesta arriba para llegar al punto en el que su sintaxis se hace clara y cómoda.

Sin embargo, el uso del operador ... sí que se ha extendido, aunque poca gente conozca la relación entre éste y el tema que nos ocupa; además del hecho de que es un operador sobrecargado, es decir, opera de forma diferente dependiendo del contexto.

El uso más común y conocido de ... es para "recolectar" los argumentos de una función en un array:

function printAllArgs( ...args ) {
    args.forEach( function(arg) {
        console.log(arg);
    });
}
printAllArgs(1, 2, 3);

La alternativa en el viejo ES5 tiene bastante menos gracia como se puede ver aquí:

function printAllArgs() {
    var args = [].slice.call(arguments);
    args.forEach( function(arg) {
        console.log(arg);
    });
}
printAllArgs(1, 2, 3);

El motivo por el que debemos llamar a [].slice.call(arguments) es que arguments no es un array real. Es un objeto en el que los nombres de propiedad son los números correspondientes a la posición correspondiente de cada argumento en la llamada a la función, y por tanto no puede acceder a ninguno de los métodos de un Array.prototype.

En cambio, ... es una abstracción, pensada para actuar de forma general, bien definida (aunque el operador esté sobrecargado) y con pocas sorpresas a la vuelta de la esquina.

En el contexto en el que uno escribiría nombres de variables, ... "recolecta" en un array los valores que queden sin asignar.

function printAllArgs( a, ...args ) {
    console.log(a);
    args.forEach( function(arg) {
        console.log(arg);
    });
}
printAllArgs(1, 2, 3);

El equivalente a lo anterior usando arguments requiere un paso extra, aunque se pueda hacer en la misma línea.

function printAllArgs( a ) {
    console.log(a);
    var args = [].slice.call(arguments).slice(1);
    args.forEach( function(arg) {
        console.log(arg);
    });
}
printAllArgs(1, 2, 3);

Sin embargo, ... también se puede utilizar para "esparcir" los valores de un array, allí donde donde pondríamos una lista de valores separados por comas.

Por ejemplo, en la lista de argumentos que pasamos al llamar a una función:

function printAllArgs( a, b, c ) {
    console.log(a);
    console.log(b);
    console.log(c);
}
printAllArgs(...[1, 2, 3]);

Si nos acordamos ahora de como desestructurábamos un array, veremos más clara la relación con el tema de esta serie de artículos:

var [x, y, z] = [1, 2, 3];
printAllArgs(x, y, z);

Usar el operador "esparcir" de esta manera es equivalente a desestructurar un array y pasar sus valores como argumentos a una función.

Esto es más útil de lo que pueda parecer en un primer momento. Por ejemplo, si tenemos que calcular el valor máximo de un array, lo que solíamos hacer, en ES5, es invocar Math.max con apply;

var precios = [38, 12, 47, 108, 23];
var masCaro = Math.max.apply(null, precios);

Aunque nos podemos acostumbrar a usar esta forma, para mi el necesario null como primer argumento de apply y el mismo apply se ponen en medio y estorban a la hora de entender esta sentencia. De nuevo, unos cuantos saltos conceptuales, retorcidos y forzados en una sola línea. El problema es lo poco declarativo de la instrucción y la atención necesaria a detalles de implementación que tienen poco que ver con lo que queremos hacer realmente.

Es mucho más limpio y claro esparcir el array como acabamos de aprender:

var precios = [38, 12, 47, 108, 23];
var masCaro = Math.max(...precios);

Y me atrevería a decir que si alguien no sabe del operador ... podrá adivinar lo que hace por el contexto sin demasiada dificultad.

Otro uso de "esparcir" que resulta muy interesante es la combinación de arrays. Antes de ES6, teníamos que hacer algo del estilo:

var a = [1, 2, 5, 6, 7];
var b = [3, 4];

a.splice.apply(a, [2, 0].concat(b));

La primera vez que vi esta técnica para concatenar arrays me explotó la cabeza y , si soy completamente honesto, aun tengo que pararme a pensar si está bien escrito cada vez que me lo encuentro.

Lo interesante es que usando el operador "esparcir" se puede hacer en una sóla línea que se entiende perfectamente:

var a = [1, 2, 5, 6, 7];
var b = [3, 4];
a.splice(2, 0, ...b);

O si te lo puedes permitir:

var b = [3, 4];
var a = [1, 2, ...b, 5, 6, 7];

A partir de la técnica anterior obtenemos una buena forma de clonar (superficialmente) un array:

var arr = [1, 2, 3];
var arrClone = [...arr];

¿Y como actúa "esparcir" sobre un objeto?

En el caso de los objetos el operador ha tardado más en llegar al estándar. De hecho aún no está en él. A día de hoy es una propuesta que está en estado 4 (stage 4 proposal) a punto de ser aceptada completamente. Lo más seguro es que acabe en la especificación del año que viene. En cualquier caso hace ya bastante tiempo que está implementada en las últimas versiones de los navegadores, en Babel y en node.js.

Su funcionalidad no es tan completa ni versatil (sólo permite "esparcir") como la que tiene cuando opera sobre arrays, pero no nos hace falta mucho más.

Clonar (de nuevo, superficialmente) objetos es así de claro y sencillo:

var usuario = {
    nombre: 'Pepito',
    apellidos: 'Grillo',
    nombreUsuario: 'PepoG'
};
var clon = {...usuario};

Mezclar objetos es así de sencillo, también:

var usuario = {
    nombre: 'Pepito',
    apellidos: 'Grillo',
    nombreUsuario: 'PepoG'
};
var direccion = {
    calle = 'Arroyo de la Media Llegua',
    numero = '7',
    piso = 3
}

var datosCompletos = {...usuario, ...direccion};

Fíjate en que no es necesario hacer una mezcla completa:

var datosParciales = {
    nombre: usuario.nombre,
    ...direccion
}

En ese caso, a veces es útil desestructurar previamente uno de los dos objetos, y usar en el objeto resultante otra característica de ES6 que nos permite omitir el nombre de la propiedad cuando queramos que coincida con el nombre de la variable que contiene el valor:

var { nombre, apellidos} = usuario;
var datosParciales = { nombre, apellidos, ...direccion };

Es importante apuntar aquí que si hay propiedades duplicadas, las que aparecen después en la asignación del objeto mezclado (sean "esparcidas" o explícitas) sobrescriben a las anteriores.

También conviene recordar que las propiedades son copiadas por referencia igual que cuando usamos Object.assign.

Irónicamente, en esta última pieza de código estamos usando la desestructuración dos veces para reestructurar la información en un sólo objeto.

Y como hemos completado el círculo y este artículo se está haciendo largo, me despido por ahora.

En la siguiente entrega hablaré de como las técnicas de desestructuración de ES6, combinados con los operadores "esparcir" y "recolectar" y algunas otras de las nuevas características de ES6 han cambiado mi forma de escribir Javascript radicalmente.

Deja un comentario

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.