15 dicembre 2021

Assegnamento di destrutturazione

Le due strutture dati più utilizzate in JavaScript sono Object e Array.

  • Gli oggetti ci consentono di creare un’unica entità che memorizza elementi nel formato chiave-valore
  • Gli array ci consentono di raccogliere elementi in elenchi ordinati.

A volte, quando li passiamo ad una funzione, potrebbe non essere necessario tutto l’oggetto/array, ma solo una parte di esso.

L’assegnamento di destrutturazione (Destructuring assignment) è una speciale sintassi che ci consente di “spacchettare” oggetti o array in gruppi di variabili; questo a volte risulta molto conveniente.

La destrutturazione funziona alla grande anche con funzioni complesse che hanno molti parametri, valori predefiniti e così via. Presto lo vedremo.

Destrutturazione di un array

Ecco un esempio di come un array viene destrutturato in variabili:

// abbiamo un array con nome e cognome
let arr = ["John", "Smith"]

// assegnamento di destrutturazione
// imposta firstName = arr[0]
// e surname = arr[1]
let [firstName, surname] = arr;

alert(firstName); // John
alert(surname);  // Smith

Ora possiamo lavorare con le variabili invece che con gli elementi dell’array.

Risulta utilissima se combinata con split o altri metodi che ritornano un array:

let [firstName, surname] = "John Smith".split(' ');
alert(firstName); // John
alert(surname);  // Smith

Come puoi vedere, la sintassi è semplice. Ci sono però molti dettagli peculiari. Vediamo altri esempi, per capirlo meglio.

“Destrutturazione” non significa “distruzione”.

Viene chiamato “assegnamento di destrutturazione” perché “destruttura” copiando gli elementi all’interno di variabili. Ma l’array in sé non viene modificato.

E’ solo un modo breve per scrivere:

// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
Ignora gli elementi usando la virgola

Gli elementi indesiderati dell’array possono essere ignorati tramite una virgola aggiuntiva:

// il secondo elemento non è necessario
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

Nel codice sopra, il secondo elemento viene saltato, il terzo viene assegnato a title, il resto degli elementi vengono ignorati (visto che per loro non ci sono variabili).

Funziona con qualsiasi iterabile alla destra

… In realtà, possiamo utilizzarlo con qualsiasi iterabile, non solamente con un array:

let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);

Funziona, perché internamente un’assegnazione di destrutturazione lavora iterando sul valore a destra. E’ una specie di “zucchero sintattico” per chiamare for..of sul valore a destra di =, assegnandone i valori.

Assegna a qualsiasi cosa ci sia dalla parte sinistra

Possiamo inserire qualsiasi cosa “assegnabile” a sinistra.

Ad esempio, la proprietà di un oggetto:

let user = {};
[user.name, user.surname] = "John Smith".split(' ');

alert(user.name); // John
alert(user.surname); // Smith
Eseguire cicli con .entries()

Nel capitolo precedente abbiamo visto il metodo Object.entries(obj).

Possiamo utilizzarlo con la destrutturazione per eseguire cicli sulle coppie chiave/valore di un oggetto:

let user = {
  name: "John",
  age: 30
};

// ciclo su chiavi/valori
for (let [key, value] of Object.entries(user)) {
  alert(`${key}:${value}`); // name:John, poi age:30
}

Un codice simile usando Map è più semplice, visto che è iterabile:

let user = new Map();
user.set("name", "John");
user.set("age", "30");

// Map itera le coppie [key, value], molto comodo per la destrutturazione
for (let [key, value] of user) {
  alert(`${key}:${value}`); // name:John, then age:30
}
Il trucco dello scambio di variabili

c’è un trucco molto conosciuto per scambiare i valori di due variabili usando l’assegnamento di destrutturazione:

let guest = "Jane";
let admin = "Pete";

// Scambio dei valori: rende guest=Pete, admin=Jane
[guest, admin] = [admin, guest];

alert(`${guest} ${admin}`); // Pete Jane (scambiati con successo!)

Nell’esempio creiamo un array temporaneo con due varibili e lo destrutturiamo immediatamente invertendo l’ordine delle variabili. Nello stesso modo potremmo scambiare i valori di più variabili.

L’operatore rest ‘…’

Di solito, se l’array è più lungo della lista a sinistra, gli elementi in eccesso vengono ignorati.

Ad esempio, qui vengono presi solo i primi 2 elementi, i restanti vengono semplicemente ignorati:

let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

// Gli elementi successivi non vengono assegnati

Se vogliamo ottenere anche tutto ciò che segue, possiamo aggiungere un altro parametro che raccoglie “il resto” utilizzando tre punti " ... ":

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

// rest è un array con gli elementi a destra, partendo dal terzo
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

La variabile rest è un array con i valori rimanenti dell’array a destra.

Possiamo utilizzare qualsiasi altro nome di variabile al posto di rest; è sufficiente accertarsi di inserire i tre punti prima del nome e di posizionarlo alla fine nell’assegnamento di destrutturazione.

let [name1, name2, ...titles] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
// now titles = ["Consul", "of the Roman Republic"]

Valori di default

Se ci sono meno elementi nell’array delle variabili da assegnare, non ci sarà alcun errore. I valori assenti vengono considerati undefined:

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

Se volessimo assegnare un nostro valore di “default”, potremmo indicarlo con la sintassi =:

// valori di default
let [name = "Guest", surname = "Anonymous"] = ["Julius"];

alert(name);    // Julius (dall'array)
alert(surname); // Anonymous (valore di default)

I valori di default possono essere anche espressioni complesse o chiamate a funzione. Verranno presi in considerazione solamente se non verrà fornito alcun valore.

Ad esempio, qui usiamo la funzione prompt per due defaults:

// viene eseguito solo il prompt per il cognome
let [name = prompt('name?'), surname = prompt('surname?')] = ["Julius"];

alert(name);    // Julius (dall'array)
alert(surname); // qualsiasi cosa provenga dal prompt

Attenzione: la funzione prompt verrà eseguita solo per il valore mancante (surname).

Destrutturazione di oggetti

L’assegnamento di destrutturazione funziona anche con gli oggetti.

La sintassi è:

let {var1, var2} = {var1:…, var2:…}

Abbiamo un oggetto alla destra dell’assegnazione, che vogliamo dividere in variabili. Nel lato sinistro abbiamo un “pattern” di proprietà corrispondenti. In questo semplice caso, abbiamo una lista di variabili raggruppate tra parentesi {...}.

Ad esempio:

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Le proprietà options.title, options.width e options.height vengono assegnate alle variabili corrispondenti.

L’ordine non ha importanza. Questo codice funzionerebbe comunque:

// cambiato l'ordine delle proprietà in let {...}
let {height, width, title} = { title: "Menu", height: 200, width: 100 }

Il pattern a sinistra potrebbe essere anche più complesso e specificare una mappatura tra proprietà e variabili.

Se volessimo assegnare una proprietà ad una variabile con un altro nome, ad esempio la proprietà options.width ad una variabile chiamata w, allora possiamo specificarlo con i due punti:

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;

// width -> w
// height -> h
// title -> title

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

I due punti specificano “cosa : va dove”. Nell’esempio sopra la proprietà width va in w, la proprietà height va in h, e title viene assegnata ad una variabile con lo stesso nome.

Per delle potenziali proprietà mancanti possiamo impostare dei valori di default utilizzando "=", come nell’esempio:

let options = {
  title: "Menu"
};

let {width = 100, height = 200, title} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

Proprio come nel caso degli array o dei parametri di funzione, i valori di default possono essere espressioni più complesse o chiamate a funzioni. Questi verranno valutati solo nel caso in cui il valore non verrà fornito.

Il codice richiederà tramite prompt la width (larghezza), ma non il title (titolo):

let options = {
  title: "Menu"
};

let {width = prompt("width?"), title = prompt("title?")} = options;

alert(title);  // Menu
alert(width);  // (qualsiasi cosa arrivi dal prompt)

Possiamo anche combinare entrambi, i due punti e l’uguaglianza:

let options = {
  title: "Menu"
};

let {width: w = 100, height: h = 200, title} = options;

alert(title);  // Menu
alert(w);      // 100
alert(h);      // 200

Se abbiamo un oggetto complesso con molte proprietà, possiamo estrarre solamente ciò che ci serve:

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

// estraiamo solamente title come proprietà
let { title } = options;

alert(title); // Menu

Il modello rest “…”

Cosa succede se l’oggetto possiede più proprietà delle variabili da noi fornite? Possiamo prenderne solamente alcune ed assegnare tutto ciò che avanza da un’altra parte?

La specifica per l’utilizzo dell’operatore rest ... fa quasi parte dello standard, ma molti browser non lo supportano ancora.

Appare cosi:

let options = {
  title: "Menu",
  height: 200,
  width: 100
};

// title = proprietà con il titolo
// rest = oggetto con il resto dei parametri
let {title, ...rest} = options;

// ora title="Menu", rest={height: 200, width: 100}
alert(rest.height);  // 200
alert(rest.width);   // 100
Catturare senza let

Negli esempi sopra le variabili vengono dichiarate appena prima di essere assegnate: let {…} = {…}. Ovviamente, potremmo anche utilizzare delle variabili già esistenti. Ma c’è un tranello.

Questo non funzionerebbe: