Inicio / Artículos

Desestructuración de JavaScript para arrays y objetos

Aprenda la desestructuración en JavaScript con explicaciones claras en inglés: omisión de elementos en arrays y valores por defecto, alias para objetos, desestructuración anidada, intercambios y parámetros de funciones (ES6+).

Editor: REACTAPP.TOP5 min de lectura1129 palabras

Fuentes: Entendiendo la desestructuración en Javascript

Destructuring significa tomar un valor estructurado y dividirlo de modo que cada parte pueda utilizarse por separado. En lugar de indexar repetidamente el mismo contenedor, se declara la forma esperada y se asignan nombres en un solo paso.

Una analogía sencilla es desempaquetar algunos artículos personales de una mochila de viaje. La mochila representa un array, objeto, cadena o cualquier estructura que se comporte como iterable. Los artículos personales son los valores individuales que se desean utilizar. Los iteradores e iterables merecen un análisis más profundo en otro momento; por ahora basta saber que los arrays y muchos componentes integrados ya ofrecen esta funcionalidad de desempaquetado.

Con este modelo mental, esas partes pueden asignarse a variables de manera más limpia que al escribir expresiones de índice separadas para cada posición:

const numbers = [ 1, 2, 3 ];

// En lugar de realizar esto:
let x = numbers[0];
let y = numbers[1];
let z = numbers[2];

// Simplemente podemos hacer esto:
let [ x, y, z ] = numbers;

console.log(x); // 1
console.log(y); // 2
console.log(z); // 3

Durante la misma asignación puedes omitir los valores que no te interesan dejando espacios vacíos entre comas. De esta manera mantienes las vinculaciones que necesitas sin tener que crear variables temporales de descarte:

const numbers = [ 1, 2, 3 ];

// x = 1, z = 3
let [ x, , z ] = numbers;

// y = 2, z = 3
let [ , y, z ] = numbers;

// x = 1, y = 2, z = 3, a = undefined
let [ x, y, z, a ] = numbers;

También puedes introducir nuevas vinculaciones con valores por defecto cuando un espacio está faltante o no definido, lo cual es útil para posiciones finales opcionales:

const numbers = [ 1, 2, 3 ];

// x = 1, y = 2, z = 3, a = 4
let [ x, y, z, a = 4 ] = numbers;

Los conjuntos anidados se desempaquetan de la misma manera: puedes acceder a un array interno sin necesidad de variables temporales que existan únicamente para eliminar una capa:

const alphanumericSequence = [ 'a', 'b', 'c', [1, 2, 3] ];

// numericSequence = [ 1, 2, 3 ]
let [ , , , numericSequence ] = alphanumericSequence;

// x = 1, y = 2, z = 3
let [ , , , [ x, y, z ] ] = alphanumericSequence;

Un truco práctico es intercambiar dos variables sin crear una temporal. La asignación por desestructuración permite cambiar ambos lados en una sola instrucción, lo que evita un error común de tipo “off-by-one” que ocurre cuando se crea manualmente una tercera variable:

let a = 1;
let b = 2;

// En lugar de hacer esto:
let temp = a;
a = b;
b = temp;

// Simplemente podemos hacer esto:
[ a, b ] = [ b, a ];

console.log(a); // 2
console.log(b); // 1

Hasta ahora, el “mochila” ha sido un array, donde la posición determina el significado. Los objetos se desempaqueten según una regla ligeramente diferente: son los nombres de las propiedades, y no el orden, los que determinan las asociaciones.

Destructuración de objetos

Cuando la estructura es un objeto, los nombres de las variables resultantes deben coincidir con los nombres de las propiedades que se extraen, a menos que se les asigne un alias. Esa es la principal diferencia con respecto al desempaquetado de arrays, donde solo importa el orden.

Un ejemplo breve muestra la regla de coincidencia de nombres en un registro plano de empleados:

const employee = {
  name: 'Julia',
  age: 27,
  job: {},
  workSchedule: {}
};

// El nombre de las variables coincide con el nombre
// de las propiedades del objeto.
let { name, age, job, workSchedule } = employee;

Cada nombre de la variable resultante coincide con una propiedad del objeto, por lo que los identificadores locales siguen siendo autoexplicativos.

Si esos nombres de propiedades resultan poco prácticos en el ámbito local o chocan con identificadores existentes, asigne alias mientras sigue utilizando las claves originales:

let {
  name:employeeName,
  age:employeeAge,
  job:employeeJob,
  workSchedule:employeeWorkSchedule
} = employee;

También se pueden aplicar valores por defecto al desempaquetar, llenando los espacios cuando una propiedad está ausente o no definida, sin necesidad de realizar una verificación separada posteriormente:

let {
  name = 'john',
  age = 18,
  job = 'worker',
  workSchedule = null
} = employee;

// Incluso utilizar un alias durante este proceso:
let {
  name:employeeName = 'john',
  age:employeeAge = 18,
  job:employeeJob = 'worker',
  workSchedule:employeeWorkSchedule = null
} = employee;

Los objetos anidados se desempaquetan con el mismo principio: declare la ruta en el patrón y asocie solo las partes que necesite:

const employee = {
  name: 'Julia',
  age: 27,
  job: {},
  workSchedule: {
    mon: {
      open: 12,
      close: 22,
    },
    tue: {
      open: 11,
      close: 23,
    },
    wed: {
      open: 23,
      close: 5,
    }
  }
};

let { workSchedule: { mon, tue, wed } } = employee;

console.log(mon); // { open: 12, close: 22 }
console.log(tue); // { open: 11, close: 23 }
console.log(wed); // { open: 23, close: 5 }

Sé conservador con los anidamientos profundos. Obtener valores a través de muchos niveles en un solo patrón afecta negativamente la legibilidad y hace que las refactoreos sean frágiles cuando cambia su estructura:

const employee = {
  name: 'Julia',
  age: 27,
  workSchedule: {
    mon: {
      open: {
        from: 12
      },
      close: {
        to: 22
      }
    }
  }
};

// Multiples niveles de anidación
let { workSchedule: { mon: { open: {from}, close: {to} } } } = employee;

console.log(from); // 12
console.log(to);   // 22

La desestructuración de objetos también es útil en los parámetros de funciones. En lugar de una larga lista posicional, acepte un objeto de opciones y desempaque sus campos en el límite, incluyendo valores por defecto:

const dispatchDelivery = (endpoint, { userId, time = '00:00', address, items = [] }) => {
  // Some action...
};

dispatchDelivery('api/dispatch-delivery', {
  userId: 3392,
  address: 'Villas del Sol',
  time: '22:30',
  items: [ 'Beers', 'Sodas', 'Doritos' ]
});

dispatchDelivery('api/dispatch-delivery', {
  userId: 5423,
  address: 'Villas del Sol'
});

El resultado es una firma más rica y fácil de leer, especialmente cuando una función necesita muchas opciones que los llamantes no siempre proporcionarán. El desestructurado es una herramienta fundamental del JavaScript moderno, y se vuelve aún más expresivo junto con el operador spread, que permite utilizar patrones de composición adicionales para clonar y combinar estructuras.

Tenga en cuenta el punto de partida del lenguaje: el desestructurado llegó con ECMAScript 2015 (ES6). Los motores más antiguos requieren un paso de transpilado o una estrategia de polyfill antes de poder utilizarlo en navegadores de producción o entornos heredados.

Lecturas relacionadas

  • Los verdaderos planos de JavaScript: prototipos, delegación y funciones — Por qué las clases son solo un síntoma sintáctico en comparación con los prototipos, cómo difiere la delegación de la herencia clásica, dónde falla este enfoque y qué ventajas ofrece el estilo funcional en JavaScript.