Operatore spread e parametri rest (`...`)

Tre punti di sospensione nel codice javascript?

Se vi è capitato di farvi questa domanda, allora probabilmente stavate osservando un operatore di javascript all’opera: l’operatore spread.

Spread, in inglese, ha il significato letterale di “espandere” o “diffondere”. Infatti, questo operatore ci permette di espandere un array o un oggetto, estraendone i dati in modo da utilizzarli in ambiti diversi, per esempio per passarli come parametri ad una funzione, o per creare un nuovo array.

La sintassi dei tre punti di sospensione può essere ingannevole, perché ha anche un altro utilizzo: la creazione dei parametri rest.

Rest, si traduce letteralmente in “resto”, o “restanti”. E’ una funzionalità che serve a raccogliere un numero non definito di parametri in un array.

Quindi abbiamo visto che i tre puntini di sospensione in javascript hanno due significati praticamente opposti l’uno all’altro. Comunque, comprendendo il funzionamento di questo operatore, non avremo grosse difficoltà a distinguerli quando li incontreremo nel codice.

La sintassi spread

Utilizzo con gli array (e altri oggetti iterabili)

Possiamo utilizzare l’operatore spread inserendo tre punti appena prima del nome di un array o di un oggetto iterabile:

function foo(a, b, c) {
  console.log(a, b, c);
}

let valori = ["Ecco", "l'operatore", "spread"];

foo(...valori); // Ecco l'operatore spread

In questo modo i valori dell’oggetto verranno estratti e utilizzati come se li avessimo inseriti uno per uno separandoli con una virgola.

Un altro modo di utilizzare questa sintassi è quello di semplificare la creazione di array. Infatti possiamo utilizzare l’operatore spread per estrarre tutti i dati di un array inserendoli in un altro. Vediamo qui sotto un esempio:

let lista_a = [0, 1, 2];
let lista_b = [...lista_a, 3, 4, 5];

console.log(lista_b); // [ 0, 1, 2, 3, 4, 5 ]

All’interno degli Array Literals (la sintassi che si usa per creare gli array usando le parentesi quadre, come nell’esempio sopra) possiamo utilizzare l’operatore spread dove vogliamo, anche più volte.

let giorniLavorativi = ["lun", "mar", "mer", "gio", "ven"];
let fineSettimana = ["sab", "dom"];

let settimana = [...giorniLavorativi, ...fineSettimana];

console.log(settimana); // [ "lun", "mar", "mer", "gio", "ven", "sab", "dom" ]

Un altro utilizzo dell’operatore spread è quello di creare delle “copie superficiali” di array o oggetti. Se copiassimo un array semplicemente usando l’uguale, array_a = array_b, in verità non creeremmo un nuovo oggetto, staremmo invece copiando solo un riferimento al primo array. Questo fa sì che le due variabili siano collegate, e ogni modifica fatta ad una di quelle variabili è automaticamente visibile anche nell’altra.

Invece, usando la sintassi spread, possiamo copiare l’array creando un oggetto separato:

let originale = [0, 1, 2];

let copiaRiferimento = originale; // copia del riferimento

let copiaValori = [...originale]; // copia superficiale (shallow copy)

originale.push(3);

console.log(copiaRiferimento); // [ 0, 1, 2, 3 ]
console.log(copiaValori); // [ 0, 1, 2 ] <-- la modifica all'oggetto originale non ha avuto effetto

Se le variabili all’interno dell’array sono degli oggetti, questi verranno comunque copiati per riferimento. Quindi qualsiasi modifica ai loro attributi sarà visibile nella copia, motivo per cui viene definita una copia “superficiale”. Comunque, le modifiche fatte all’array di per sé (aggiunta o rimozione di elementi, ordinamento, ecc…) saranno totalmente separate.

let utenti = [
  {
    nome: "Andrea",
  },
];

let nuoviUtenti = [...utenti];

utenti[0].name = "Giovanni";
console.log(nuoviUtenti[0].name); // Giovanni <-- l'oggetto di per sé è stato copiato come riferimento

utenti.pop();

console.log(utenti); // []
console.log(nuoviUtenti); // [ { ... } ] <-- Questo dimostra che l'Array di per sé non è stato copiato come riferimento

Le proprietà spread negli oggetti

Negli oggetti, le proprietà spread ci vengono in aiuto quando vogliamo unire due o più oggetti, oppure fare una “copia superficiale” di un oggetto (con le stesse limitazioni viste prima per gli array).

La sintassi è praticamente identica a quella vista in precedenza:

let utenteBase = {
  nome: "",
  password: "",
};

let amministratore = {
  ...utenteBase,
  permessi: [],
};

console.log(amministratore); // { nome: '', password: '', permessi: [] }

Questa sintassi, in effetti, viene definita in inglese “spread properties”, per differenziarla dall’operatore spread, che è utilizzabile solo per gli oggetti iterabili (e il nostro oggetto dell’esempio sopra non lo è). Infatti, se volessimo utilizzare la sintassi spread all’interno di una funzione come abbiamo fatto per gli array, in questo caso ci verrebbe restituito un errore:

let utente = {
  nome: "",
  password: "",
};

console.log(...utente); // TypeError: utente non è iterabile

Riprendendo l’esempio dell’utente e dell’amministratore, è anche interessante quello che succede se inseriamo un valore che esisteva già nel primo oggetto:

let utenteBase = {
  nome: "",
  password: "",
};

let amministratore = {
  ...utenteBase,

  nome: "admin",
  permessi: [],
};

console.log(amministratore); // { nome: 'admin', password: '', permessi: [] }

Come possiamo notare, quando proviamo ad aggiungere un attributo che esista già (in questo caso “nome”), il valore precedente verrà sovrascritto.

I parametri rest

Quando dichiariamo delle funzioni in javascript, dobbiamo indicare il numero di parametri che vogliamo accettare. Eppure, in alcune funzioni di base, sembra che possiamo andare avanti ad aggiungere dei parametri all’infinito! Questo perché, già da molto tempo, i parametri delle funzioni vengono inseriti in un array chiamato “arguments”, al quale possiamo accedere in qualsiasi funzione senza necessità di dichiararlo.

Questo array, però, ha diversi problemi. Prima di tutto, non è un vero array, ma un tipo speciale di oggetto. Questo ci impedisce di utilizzare le funzioni che utilizzeremmo di solito con gli array. Inoltre, contiene anche i parametri che abbiamo già indicato nella dichiarazione e ai quali abbiamo già assegnato dei nomi.

Per risolvere il problema, sono stati implementati i parametri rest. In pratica, aggiungendo i tre punti di sospensione all’interno della dichiarazione dei parametri di una funzione, possiamo racchiudere in una variabile tutti i parametri che sarebbero di troppo.

Ecco un esempio:

function parametriRest(primo, secondo, ...altri) {
  console.log(primo); // primo
  console.log(secondo); // secondo
  console.log(altri); // [ 'terzo', 'quarto', 'ecc...']

  // In passato (ma funzionante anche oggi)
  console.log(arguments); // Arguments(5) ["primo", "secondo", "terzo", "quarto", "ecc..."]
}

parametriRest("primo", "secondo", "terzo", "quarto", "ecc...");

Adesso possiamo utilizzare la variabile altri come utilizzeremmo un qualsiasi array.

Spero che questa possa essere una spiegazione abbastanza esauriente su cosa siano e come si usino i parametri rest e la sintassi spread in javascript. Comunque, se volete altre informazioni, potete sempre andare a vedere la pagina MDN sulla spread syntax e sui parametri rest.

Buon lavoro!