Las dos estructuras de datos más utilizadas en JavaScript son Object
y Array
.
Los objetos nos permiten crear una entidad única que almacena elementos de datos por clave.
Las matrices nos permiten recopilar elementos de datos en una lista ordenada.
Sin embargo, cuando los pasamos a una función, es posible que no los necesitemos todos. Es posible que la función solo requiera ciertos elementos o propiedades.
La asignación de desestructuración es una sintaxis especial que nos permite "desempaquetar" matrices u objetos en un montón de variables, ya que a veces eso es más conveniente.
La desestructuración también funciona bien con funciones complejas que tienen muchos parámetros, valores predeterminados, etc. Pronto veremos eso.
A continuación se muestra un ejemplo de cómo se desestructura una matriz en variables:
// tenemos un array con nombre y apellido let arr = ["Juan", "Smith"] // tarea de desestructuración // establece nombre = arreglo[0] // y apellido = arreglo[1] let [nombre, apellido] = arr; alerta(nombre); // John alerta(apellido); // herrero
Ahora podemos trabajar con variables en lugar de miembros de la matriz.
Se ve muy bien cuando se combina con split
u otros métodos de devolución de matrices:
let [nombre, apellido] = "John Smith".split(' '); alerta(nombre); // John alerta(apellido); // herrero
Como puede ver, la sintaxis es simple. Sin embargo, hay varios detalles peculiares. Veamos más ejemplos para entenderlo mejor.
"Desestructurar" no significa "destructivo".
Se llama "asignación desestructurante" porque "desestructura" copiando elementos en variables. Sin embargo, la matriz en sí no se modifica.
Es solo una forma más corta de escribir:
// let [nombre, apellido] = arr; let nombre = arr[0]; let apellido = arr[1];
Ignorar elementos usando comas
Los elementos no deseados de la matriz también se pueden descartar mediante una coma adicional:
// el segundo elemento no es necesario let [firstName, , title] = ["Julio", "César", "Cónsul", "de la República Romana"]; alerta (título); // Cónsul
En el código anterior, se omite el segundo elemento de la matriz, el tercero se asigna al title
y el resto de los elementos de la matriz también se omiten (ya que no hay variables para ellos).
Funciona con cualquier iterable del lado derecho.
…En realidad, podemos usarlo con cualquier iterable, no solo con matrices:
sea [a, b, c] = "abc"; // ["a", "b", "c"] let [uno, dos, tres] = new Set([1, 2, 3]);
Eso funciona, porque internamente una tarea desestructurante funciona iterando sobre el valor correcto. Es una especie de azúcar de sintaxis para solicitar for..of
of sobre el valor a la derecha de =
y asignar los valores.
Asignar a cualquier cosa en el lado izquierdo
Podemos usar cualquier “asignable” en el lado izquierdo.
Por ejemplo, una propiedad de objeto:
dejar usuario = {}; [nombre.de.usuario, nombre.de.usuario] = "John Smith".split(' '); alerta(nombre.usuario); // John alerta(usuario.apellido); // herrero
Bucle con .entries()
En el capítulo anterior, vimos el método Object.entries(obj).
Podemos usarlo con desestructuración para recorrer las claves y valores de un objeto:
dejar usuario = { nombre: "Juan", edad: 30 }; // recorre las claves y valores for (let [clave, valor] de Object.entries(usuario)) { alerta(`${clave}:${valor}`); // nombre:John, luego edad:30 }
El código similar para un Map
es más simple, ya que es iterable:
dejar usuario = nuevo Mapa(); usuario.set("nombre", "Juan"); usuario.set("edad", "30"); // El mapa se itera como pares [clave, valor], muy conveniente para desestructurar for (let [clave, valor] del usuario) { alerta(`${clave}:${valor}`); // nombre:John, luego edad:30 }
Truco de intercambio de variables
Existe un truco bien conocido para intercambiar valores de dos variables mediante una asignación desestructurante:
dejar invitada = "Jane"; let admin = "Pete"; // Intercambiemos los valores: make guest=Pete, admin=Jane [invitado, administrador] = [administrador, invitado]; alerta(`${invitado} ${admin}`); // Pete Jane (¡intercambiado con éxito!)
Aquí creamos una matriz temporal de dos variables y la desestructuramos inmediatamente en orden intercambiado.
Podemos intercambiar más de dos variables de esta manera.
Normalmente, si la matriz es más larga que la lista de la izquierda, se omiten los elementos "extra".
Por ejemplo, aquí sólo se toman dos elementos y el resto simplemente se ignora:
let [nombre1, nombre2] = ["Julio", "César", "Cónsul", "de la República Romana"]; alerta(nombre1); // julio alerta(nombre2); // César // No se asignan más elementos a ninguna parte
Si también queremos recopilar todo lo que sigue, podemos agregar un parámetro más que obtenga “el resto” usando tres puntos "..."
:
let [nombre1, nombre2, ...descanso] = ["Julio", "César", "Cónsul", "de la República Romana"]; // el resto es una serie de elementos, comenzando desde el tercero alerta(descanso[0]); // Cónsul alerta(descanso[1]); // de la República Romana alerta (descanso.longitud); // 2
El valor del rest
es la matriz de los elementos restantes de la matriz.
Podemos usar cualquier otro nombre de variable en lugar de rest
, solo asegúrese de que tenga tres puntos delante y que sea el último en la tarea de desestructuración.
let [nombre1, nombre2, ...títulos] = ["Julio", "César", "Cónsul", "de la República Romana"]; // ahora títulos = ["Cónsul", "de la República Romana"]
Si la matriz es más corta que la lista de variables de la izquierda, no habrá errores. Los valores ausentes se consideran indefinidos:
let [nombre, apellido] = []; alerta(nombre); // indefinido alerta(apellido); // indefinido
Si queremos un valor "predeterminado" para reemplazar el que falta, podemos proporcionarlo usando =
:
// valores predeterminados let [nombre = "Invitado", apellido = "Anónimo"] = ["Julius"]; alerta(nombre); // Julius (de la matriz) alerta(apellido); // Anónimo (usado por defecto)
Los valores predeterminados pueden ser expresiones más complejas o incluso llamadas a funciones. Se evalúan sólo si no se proporciona el valor.
Por ejemplo, aquí usamos la función prompt
para dos valores predeterminados:
// ejecuta sólo el mensaje de apellido let [nombre = mensaje('nombre?'), apellido = mensaje('apellido?')] = ["Julius"]; alerta(nombre); // Julius (de la matriz) alerta(apellido); // cualquier mensaje que llegue
Tenga en cuenta: el prompt
se ejecutará solo para el valor faltante ( surname
).
La tarea de desestructuración también funciona con objetos.
La sintaxis básica es:
sea {var1, var2} = {var1:…, var2:…}
Deberíamos tener un objeto existente en el lado derecho, que queremos dividir en variables. El lado izquierdo contiene un "patrón" similar a un objeto para las propiedades correspondientes. En el caso más simple, es una lista de nombres de variables en {...}
.
Por ejemplo:
dejar opciones = { título: "Menú", ancho: 100, altura: 200 }; let {título, ancho, alto} = opciones; alerta(título); // Menú alerta (ancho); // 100 alerta (altura); // 200
Las propiedades options.title
, options.width
y options.height
se asignan a las variables correspondientes.
El orden no importa. Esto también funciona:
// cambió el orden en let {...} let {alto, ancho, título} = {título: "Menú", alto: 200, ancho: 100}
El patrón del lado izquierdo puede ser más complejo y especificar el mapeo entre propiedades y variables.
Si queremos asignar una propiedad a una variable con otro nombre, por ejemplo, hacer options.width
vaya a la variable llamada w
, entonces podemos establecer el nombre de la variable usando dos puntos:
dejar opciones = { título: "Menú", ancho: 100, altura: 200 }; // { propiedad fuente: variable objetivo } let {ancho: w, alto: h, título} = opciones; // ancho -> w // altura -> h // título -> título alerta(título); // Menú alerta(w); // 100 alerta(h); // 200
Los dos puntos muestran "qué: va adónde". En el ejemplo anterior, el width
de la propiedad va a w
, height
de la propiedad va a h
y title
se asigna al mismo nombre.
Para propiedades potencialmente faltantes, podemos establecer valores predeterminados usando "="
, como este:
dejar opciones = { título: "Menú" }; let {ancho = 100, alto = 200, título} = opciones; alerta(título); // Menú alerta (ancho); // 100 alerta (altura); // 200
Al igual que con las matrices o los parámetros de funciones, los valores predeterminados pueden ser cualquier expresión o incluso llamadas a funciones. Serán evaluados si no se proporciona el valor.
En el siguiente código, prompt
solicita el width
, pero no el title
:
dejar opciones = { título: "Menú" }; let {ancho = mensaje ("ancho?"), título = mensaje ("título?")} = opciones; alerta(título); // Menú alerta (ancho); // (cualquiera que sea el resultado del mensaje)
También podemos combinar los dos puntos y la igualdad:
dejar opciones = { título: "Menú" }; let {ancho: w = 100, alto: h = 200, título} = opciones; alerta(título); // Menú alerta(w); // 100 alerta(h); // 200
Si tenemos un objeto complejo con muchas propiedades, podemos extraer sólo lo que necesitamos:
dejar opciones = { título: "Menú", ancho: 100, altura: 200 }; // solo extraemos el título como una variable let {título} = opciones; alerta(título); // Menú
¿Qué pasa si el objeto tiene más propiedades que variables? ¿Podemos tomar algunos y luego asignar el “resto” a alguna parte?
Podemos usar el patrón resto, tal como lo hicimos con las matrices. No es compatible con algunos navegadores antiguos (IE, use Babel para rellenarlo), pero funciona en los modernos.
Se parece a esto:
dejar opciones = { título: "Menú", altura: 200, ancho: 100 }; // título = propiedad llamada título // resto = objeto con el resto de propiedades let {título, ...descanso} = opciones; // ahora título="Menú", resto={alto: 200, ancho: 100} alerta (descanso.altura); // 200 alerta(rest.ancho); // 100
Te tengo si no hay let
En los ejemplos anteriores, las variables se declararon directamente en la asignación: let {…} = {…}
. Por supuesto, también podríamos usar variables existentes, sin let
. Pero hay un problema.
Esto no funcionará:
let título, ancho, alto; // error en esta línea {título, ancho, alto} = {título: "Menú", ancho: 200, alto: 100};
El problema es que JavaScript trata {...}
en el flujo de código principal (no dentro de otra expresión) como un bloque de código. Estos bloques de código se pueden utilizar para agrupar declaraciones, como esta:
{ // un bloque de código dejar mensaje = "Hola"; //... alerta (mensaje); }
Entonces aquí JavaScript supone que tenemos un bloque de código, por eso hay un error. En lugar de eso, queremos desestructuración.
Para mostrarle a JavaScript que no es un bloque de código, podemos envolver la expresión entre paréntesis (...)
:
let título, ancho, alto; // está bien ahora ({título, ancho, alto} = {título: "Menú", ancho: 200, alto: 100}); alerta (título); // Menú
Si un objeto o una matriz contiene otros objetos y matrices anidados, podemos usar patrones del lado izquierdo más complejos para extraer porciones más profundas.
En el código siguiente, options
tienen otro objeto en el size
de la propiedad y una matriz en los items
de la propiedad. El patrón en el lado izquierdo de la tarea tiene la misma estructura para extraer valores de ellos:
dejar opciones = { tamaño: { ancho: 100, altura: 200 }, artículos: ["Pastel", "Donut"], adicional: cierto }; // asignación de desestructuración dividida en varias líneas para mayor claridad dejar { tamaño: { // ponga el tamaño aquí ancho, altura }, elementos: [elemento1, elemento2], // asigna elementos aquí título = "Menú" // no presente en el objeto (se utiliza el valor predeterminado) } = opciones; alerta(título); // Menú alerta (ancho); // 100 alerta (altura); // 200 alerta(elemento1); // Pastel alerta(elemento2); // Rosquilla
Todas las propiedades del objeto options
excepto extra
, que está ausente en la parte izquierda, se asignan a las variables correspondientes:
Finalmente, tenemos width
, height
, item1
, item2
y title
del valor predeterminado.
Tenga en cuenta que no hay variables para size
y items
, ya que en su lugar tomamos su contenido.
Hay ocasiones en las que una función tiene muchos parámetros, la mayoría de los cuales son opcionales. Esto es especialmente cierto para las interfaces de usuario. Imagine una función que crea un menú. Puede tener un ancho, un alto, un título, una lista de elementos, etc.
Aquí hay una mala manera de escribir una función de este tipo:
función showMenu(título = "Sin título", ancho = 200, alto = 100, elementos = []) { //... }
En la vida real, el problema es cómo recordar el orden de los argumentos. Normalmente, los IDE intentan ayudarnos, especialmente si el código está bien documentado, pero aún así… Otro problema es cómo llamar a una función cuando la mayoría de los parámetros están bien de forma predeterminada.
¿Como esto?
// indefinido donde los valores predeterminados están bien showMenu("Mi menú", indefinido, indefinido, ["Elemento1", "Elemento2"])
Eso es feo. Y se vuelve ilegible cuando tratamos con más parámetros.
¡La desestructuración viene al rescate!
Podemos pasar parámetros como un objeto y la función los desestructura inmediatamente en variables:
// pasamos el objeto a la función dejar opciones = { título: "Mi menú", elementos: ["Elemento1", "Elemento2"] }; // ...e inmediatamente lo expande a variables función showMenu({título = "Sin título", ancho = 200, alto = 100, elementos = []}) { // título, elementos – tomados de opciones, // ancho, alto – valores predeterminados utilizados alerta(`${título} ${ancho} ${alto}`); // Mi Menú 200 100 alerta (elementos); // Artículo1, Artículo2 } mostrarMenú(opciones);
También podemos utilizar una desestructuración más compleja con objetos anidados y asignaciones de dos puntos:
dejar opciones = { título: "Mi menú", elementos: ["Elemento1", "Elemento2"] }; función mostrarMenú({ título = "Sin título", ancho: w = 100, // el ancho va a w altura: h = 200, // la altura va a h elementos: [elemento1, elemento2] // el primer elemento del elemento va al elemento1, el segundo al elemento2 }) { alerta( `${título} ${w} ${h}` ); // Mi Menú 100 200 alerta( elemento1 ); // Artículo1 alerta (elemento2); // Artículo2 } mostrarMenú(opciones);
La sintaxis completa es la misma que para una tarea desestructurante:
función({ propiedad entrante: varName = defaultValue ... })
Luego, para un objeto de parámetros, habrá una variable varName
para la propiedad incomingProperty
, con defaultValue
por defecto.
Tenga en cuenta que dicha desestructuración supone que showMenu()
sí tiene un argumento. Si queremos todos los valores por defecto, entonces debemos especificar un objeto vacío:
mostrarMenú({}); // ok, todos los valores son predeterminados mostrarMenú(); // esto daría un error
Podemos solucionar esto haciendo que {}
sea el valor predeterminado para todo el objeto de parámetros:
función showMenu({ título = "Menú", ancho = 100, alto = 200 } = {}) { alerta(`${título} ${ancho} ${alto}`); } mostrarMenú(); // Menú 100 200
En el código anterior, todo el objeto de argumentos es {}
de forma predeterminada, por lo que siempre hay algo que desestructurar.
La asignación de desestructuración permite asignar instantáneamente un objeto o matriz a muchas variables.
La sintaxis completa del objeto:
let {prop: varName = defaultValue, ...rest} = objeto
Esto significa que la propiedad prop
debe ir en la variable varName
y, si no existe dicha propiedad, entonces se debe usar el valor default
.
Las propiedades del objeto que no tienen asignación se copian al rest
del objeto.
La sintaxis completa de la matriz:
let [elemento1 = valor predeterminado, elemento2, ...descanso] = matriz
El primer elemento va al item1
; el segundo va al item2
y el resto hace que la matriz rest
.
Es posible extraer datos de matrices/objetos anidados, para ello el lado izquierdo debe tener la misma estructura que el derecho.
importancia: 5
Tenemos un objeto:
dejar usuario = { nombre: "Juan", años: 30 };
Escribe la tarea de desestructuración que dice:
propiedad name
en el name
de la variable.
years
de propiedad en la variable age
.
propiedad isAdmin
en la variable isAdmin
(falso, si no existe dicha propiedad)
A continuación se muestra un ejemplo de los valores después de su tarea:
let usuario = { nombre: "John", años: 30 }; // tu código al lado izquierdo: // ... = usuario alerta (nombre); // John alerta (edad); // 30 alerta( esAdmin ); // FALSO
dejar usuario = { nombre: "Juan", años: 30 }; let {nombre, años: edad, isAdmin = false} = usuario; alerta (nombre); // John alerta (edad); // 30 alerta( esAdmin ); // FALSO
importancia: 5
Hay un objeto salaries
:
dejar salarios = { "Juan": 100, "Pete": 300, "María": 250 };
Cree la función topSalary(salaries)
que devuelva el nombre de la persona mejor pagada.
Si salaries
están vacíos, debería devolver null
.
Si hay varias personas mejor pagadas, devuelva cualquiera de ellas.
PD: Utilice Object.entries
y desestructuración para iterar sobre pares clave/valor.
Abra una caja de arena con pruebas.
función topSalary(salarios) { let maxSalary = 0; let maxName = nulo; for(const [nombre, salario] de Object.entries(salarios)) { if (salariomax <salario) { maxSalario = salario; maxName = nombre; } } devolver nombre máximo; }
Abra la solución con pruebas en un sandbox.