Desestructuración en Javascript
Desestructuración en Javascript
docx 1 de 16
Introducción
La desestructuración es otra de las novedades potentes del nuevo estándar ES6 (ECMAScript 2015). Si bien, por su
definición, no es una funcionalidad que pueda parecer compleja, sí lo es cuando comenzamos a profundizar en sus
posibilidades. La razón de esto es la habitual en Javascript: el lenguaje, debido a su propia estructura, puede ser
extremadamente flexible y, gracias a esa versatilidad, toda nueva característica puede ser explotada hasta sus
límites teóricos.
Veamos en esta primera parte cómo funciona la desestructuración en su modo más básico con varios ejemplos antes
de meternos en construcciones de mayor envergadura.
¡Vamos a ello!
La idea se ve fácilmente: usando una única instrucción, tomamos un conjunto de variables (bloque de la izquierda) y
le asignamos un conjunto de valores (bloque de la derecha).
Lo que hace de esta estructura algo que da mucho juego es cómo formemos los bloques a cada lado de la igualdad,
algo que iremos viendo poco a poco.
Declaración de variables
Como ocurre con cualquier variable en Javascript, ésta ha de ser declarada para evitar contaminar el espacio global.
Durante la desestructuración, la declaración puede realizarse tanto en la misma instrucción como con anterioridad.
También es posible utilizar cualquiera de las formas actuales (visibilidad) con que contamos para declarar variables
en el lenguaje:
var a, b;
let c, d;
[ a, b, c, d ] = [ 'La', 'donna', 'e', 'mobile' ];
var [ e, f ] = [ 'cual', 'piuma' ];
let [ g, h ] = [ 'al', 'vento' ];
console.info( a, b, c, d, e, f, g, h );
// La donna e mobile cual piuma al vento
NOTA: Para saber más sobre la declaración con LET, recomiendo el artículo donde analicé esta instrucción en
profundidad.
557621779.docx 2 de 16
Con las constantes, sin embargo, no pueden definirse previamente para asignar su valor después:
const ONE; // SyntaxError
[ ONE ] = [ 'foo' ];
Eso ocurre porque las constantes deben iniciarse con un valor. Sí funciona si realizamos la declaración directamente
junto a la desestructuración:
const [ ONE, TWO ] = [ 'foo', 'bar' ];
console.info( ONE, TWO ); // foo bar
NOTA: Para saber más sobre las constantes, recomiendo leer el artículo donde las analicé en profundidad.
var obj = {
foo: 'Hello',
bar: 'World'
};
var { foo, bar } = obj;
console.info( foo, bar ); // Hello World
Si el objeto no contiene las claves que solicitamos, se les asocia automáticamente el valor ‘undefined‘:
var { a, b } = obj;
console.info( a, b ); // undefined undefined
Si nuestras variables ya han sido declaradas, no podremos utilizar solo llaves para indicar el primer conjunto:
var obj = {
foo: 'Hello',
bar: 'World'
};
var foo, bar;
{ foo, bar } = obj; //SyntaxError
Esto es así porque las llaves están indicando un bloque/contexto (lo que sería una estructura perfectamente válida
en Javascript). Para que funcione, necesitamos enmarcar toda la instrucción entre paréntesis:
( { foo, bar } = obj );
console.info( foo, bar ); // Hello World
Ejemplo caso 1:
var [ a, b, c ] = [ 'Hello', 'World' ];
console.info( a, b, c ); // Hello World undefined
Ejemplo caso 2:
var [ a, b ] = [ 'La', 'donna', 'e', 'mobile' ];
console.info( a, b ); // La donna
Ignorando valores
También podemos utilizar la elisión para ignorar valores tanto en uno como en otro conjunto:
var [ a, , b, , c ] = [ 'la', 'donna', 'e', 'mobile', 'cual', 'piuma' ];
console.info( a, b, c ); // la e cual
NOTA: la elisión (dos comas consecutivas), se interpreta como un hueco e ignora su valor correspondiente en el otro
conjunto. ¡Incluso podemos ignorar todo el conjunto!:
Lo anterior funciona de modo similar cuando la elisión se realiza en el conjunto de los valores:
var [ a, b, c, d ] = [ 'la', , 'donna', , 'e', 'mobile' ];
console.info( a, b, c, d ); // la undefined donna undefiend
En el ejemplo anterior, nuestra función devolvía los valores. Sin embargo, parece que no es posible utilizar una
función como fuente para las asignaciones:
557621779.docx 4 de 16
var a, b, c, d;
var foo = () => [ a, b, c, d ];
var bar = () => [ 'la', 'donna', 'e', 'mobile' ];
foo() = [ 'La', 'donna', 'e', 'mobile' ];
// Invalid assignment
[ foo() ] = [ 'La', 'donna', 'e', 'mobile' ];
// Invalid destructuring target
[ foo() ] = [ bar() ];
// Invalid destructuring target
La función ‘upper’ toma todos los argumentos que se le pasen vía el operador de propagación o arrastre y los pasa a
mayúsculas. Se trataría de una Función Pura que podemos utilizar para asignar valores en batería gracias a la
desestructuración.
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!
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
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:
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 );
}
var books = [
{
title: "La vida del lazarillo de Tormes",
author: "Anonymous",
published: "1554"
557621779.docx 7 de 16
}, {
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 );
}
Operador de arrastre
Del mismo modo que podemos utilizar el operador de acarreo/arrastre en funciones, puede aplicarse también
durante la desestructuración:
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:
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
557621779.docx 10 de 16
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:
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…
Introducción
En el artículo anterior pudimos ver cómo los ‘rest parameter‘ nos permiten manejar un número indeterminado de
argumentos en nuestras funciones. Pero podemos utilizar este mismo concepto para, en modo inverso, convertir un
array en una serie de argumentos para una función. Para ello, ECMAScript 6 incorpora el Operador de Propagación,
el cual permite exactamente eso.
Veamos cómo utilizarlo…
Ejemplo básico
El Operador de Propagación, o spread operator, se compone del nombre de nuestro array precedido por tres
puntos:
var foo = [ 'En', 'un', 'lugar', 'de', 'la', 'Mancha' ];
console.info( ...foo );
// En un lugar de la Mancha
En este sencillo ejemplo, estamos cogiendo nuestro array para procesar cada uno de sus elementos como
argumentos de la propia función ‘console.info’, por lo que el resultado sería equivalente a escribir:
La idea es la expuesta: descomponemos el array en elementos y pasamos cada uno de ellos como argumentos a
una función. La comodidad aquí es que no necesitamos conocer el número de elementos que contiene el array ni
crear bucles que lo recorran, o usar otras funcionalidades del objeto Array.
Concatenar Arrays
Esta funcionalidad del operador de propagación puede ser especialmente útil por ejemplo para concatenar arrays.
Por ejemplo:
var foo = [ 'En', 'un', 'lugar', 'de', 'la', 'Mancha' ],
bar = [ 'de', 'cuyo', 'nombre', 'no', 'quiero', 'acordarme' ],
// Old Style
oldStyle = foo.concat( bar ),
557621779.docx 12 de 16
// ECMAScript 6 style
ES6Style = [ ...foo, ...bar ];
console.info( oldStyle );
// [ "En", "un", "lugar", "de", "la", "Mancha", "de",
"cuyo", "nombre", "no", "quiero", "acordarme" ]
console.info( ES6Style );
// [ "En", "un", "lugar", "de", "la", "Mancha", "de",
"cuyo", "nombre", "no", "quiero", "acordarme" ]
Como vemos, hemos utilizado las dos formas: la clásica del concat, y el método más ‘reciente’ (y más legible) que
nos permite ES6.
El funcionamiento en este caso es similar: se han cogido cada uno de los elementos de los arrays que se quieren
procesar para pasarlos como argumentos de un tercero. El resultado lógico es que obtenemos una nueva matriz con
cada uno de los elementos independientes de sus dos fuentes. Simple y elegante!
Si por el contrario omitimos valores, el intérprete Javascript los reemplazará por ‘undefined’, pero no lanzará un
mensaje de error:
var book4 = [ 'JavaScript Patterns' ];
saveBook( ...book4 );
// The book JavaScript Patterns by undefined
// published by undefined has been added to database!
Este último ejemplo, que podría provocar errores o incosistencias, podría quedar mucho más completo si añadimos
a nuestros argumentos un valor por defecto tal y como vimos en el post Cómo asignar valores por defecto a los
argumentos de una función (revisión ES6):
Por lo que ahora, si omitimos argumentos, obtenemos al menos un valor por defecto en lugar del problemático
‘undefined’:
var newBook = [ 'Node.js in Action' ];
saveBook( ...newBook );
// The book Node.js in Action by Unknown published by Unknown has been added to database!
Para evitar que un error de este tipo pueda bloquear nuestra aplicación, habría quizá que recurrir a algún tipo de
comprobación previa, o a coercionar el tipo de la variable sobre la que queremos operar… Por ejemplo:
var foo = { foo: 'Hello World', bar: 'Goodbye Lenin' },
bar = [ 'La', 'donna', 'e', 'mobile' ];
// Checking
foo.length && ( console.info( ...foo ) ); // Nothing happens
bar.length && ( console.info( ...bar ) ); // La donna e mobile
// Coercing
foo.length || ( foo = [] );
console.info( ...foo ); // (nothing to show)
557621779.docx 14 de 16
No son soluciones a priori elegantes, pero evitamos así el error: comprobamos si la variable es ‘iterable’
preguntando por su atributo ‘length’; en caso de que no lo posea, o bien no llamamos a nuestra función (primer
método del ejemplo), o bien reescribimos su valor por un array vacío (segundo método).
El sinsentido del ejemplo es que convertimos un array en valores individuales para luego volverlos a recomponer. Lo
cierto es que no encuentro ningún escenario donde esto podría ser interesante, pero aquí queda como
Polyfill
Como viene siendo habitual, para aquellos navegadores antiguos que no soporten la nueva especificación, hay que
modificar este operador de propagación por su forma extendida. En este caso, sin embargo, es más sencillo que con
funcionalidades más complejas. Basta modificar la llamada simple a nuestra a nuestra función y recurrir al
método apply. Volviendo a nuestro ejemplo anterior de saveBook, basta con:
Conclusión
El operador de propagación puede ser una herramienta interesante, y muy potente, para trabajar con funciones que
reciben parámetros desde fuentes externas. Puede ser por ejemplo muy útil cuando trabajamos con la respuesta
que nos proporciona una API de terceros, o para manipular los datos que recogemos de un formulario. Usado de
forma más exótica, también nos permite concatenar arrays…
Como siempre se dice por aquí, cualquier innovación en el lenguaje es bienvenida, y esta no va a ser menos!
Una de las nuevas funcionalidades que nos permite ECMAScript 6 es el poder agrupar los argumentos que llegan a
nuestra función en un array. Es lo que se conoce como Rest Parameters, y aunque está estrechamente relacionado
con el objeto arguments que ya hemos visto en varias ocasiones (aquí o aquí), presenta algunas diferencias.
Echemos un vistazo!
Sintaxis básica
La forma de implementar esta funcionalidad es sencilla y recuerda a otros lenguajes como Ruby:
var myFunc = function ( foo, bar, ...theArgs ) {
// ...
}
557621779.docx 15 de 16
Esta función, con sus peculiares tres puntos delante del tercer argumento, estarían indicándole al intérprete que ese
valor debe ser un array compuesto por los parámetros que lleguen desde la llamada siguiendo la siguiente lógica:
El primer parámetro que llegue, se mapea como foo.
El segundo parámetro que llegue, se mapea como bar.
El resto de parámetros, se guardarán dentro de un array definido como theArgs.
Un detalle interesante es que una vez que utilizamos esta funcionalidad, dejamos de tener acceso al objeto
arguments. Si preguntamos dentro de la función por dicho objeto, obtendríamos el siugiente mensaje de error:
SyntaxError: 'arguments' object may not be used in conjunction with a rest parameter
Un verdadero Array
Una característica importante es que estos ‘rest arguments’ si constituyen un verdadero array, a diferencia de
arguments, que dan lugar a un «objeto similar a un array» que tiene algunas de sus propiedades como length pero
que carece de otras como shift o pop.
Como vemos, en la segunda si tenemos un verdadero array, lo que nos permite evitar snippets como el que hemos
usado hasta ahora del tipo:
var myArgsFunc = function () {
var args = Array.prototype.slice.call(arguments);
console.info( Object.prototype.toString.call( args ) );
};
myArgsFunc( 'myParam1', 'myParam2', 'myParam3', 'myParam4' ); // [ Object Array ]
557621779.docx 16 de 16
NOTA: Para más información sobre el método Object.prototype para comprobar el tipo de objetos, ver el artículo
«Cómo obtener el tipo de datos preciso de una variable en Javascript«.
Otro detalle importante, es que no podemos asignar un valor por defecto a este tipo de objeto, ya que en caso de
omisión, el intérprete ya asigna un array vacío:
var myRestFunc = function ( ...myArgs ) {
console.info( myArgs );
};
myRestFunc(); // []
Si intentamos forzarlo con la también nueva funcionalidad ECMAScript 6 que ya comentamos aquí, obtendremos un
error:
var myRestFunc = function ( ...myArgs = [ 'default1', 'default2' ] ) {
//...
};
myRestFunc(); // SyntaxError: rest parameter may not have a default
Polyfill
Para seguir la costumbre, mostramos a continuación el polyfill para aquellos navegadores que aún no interpreten el
estándar ES6:
var myPolyfillRestFunc = function ( x /* ...y */ ) {
for ( var y = [], _y = 1; _y < arguments.length; _y++ ) {
y[ _y - 1 ] = arguments[ _y ] ;
}
console.info( 'x: ', x );
console.info( 'y: ', y );
};
myPolyfillRestFunc( 'myParam1' );
// x: myParam1
// y: []
myPolyfillRestFunc( 'myParam1', 'myParam2', 'myParam3', 'myParam4', 'myParam5' );
// x: myParam1
// y: ["myParam2", "myParam3", "myParam4", "myParam5"]
La magia la hacemos, como no, a través del viejo arguments a partir del cual, gracias a un bucle, vamos rellenando
nuestro array.
Conclusión
Esta nueva funcionalidad viene a cubrir un aspecto del lenguaje que hasta el momento tenía que solucionarse con
métodos cuanto mínimo exóticos. Puede que a priori no recurramos demasiado a esta forma de recoger los
argumentos de nuestras funciones, o le encontremos directamente utilidad, pero el hecho de que podamos hacerlo
en caso necesario de forma nativa, siempre es positivo.