Articolo originale: JavaScript Rest vs Spread Operator – What’s the Difference? di Oluwatobi Sofela

Tradotto e adattato da: Ilenia Magoni

In JavaScript, i tre puntini (...) vengono usati sia per l'operatore rest che per l'operatore spread, ma questi due operatori non sono affatto la stessa cosa.

La differrenza principale è che l'operatore rest mette il resto di alcuni valori dati dall'utente in un array JavaScript, mentre l'operatore spread espande gli iterabili in elementi individuali.

Per esempio, considera questo codice che fa uso dell'operatore rest per includere dei valori in un array:

// Usa l'operatore rest per includere il resto di certi valori dati dall'utente in un array:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

// Invoca la funzione myBio passando cinque argomenti ai suoi parametri:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");

// L'invocazione qui sopra restituirà:
["CodeSweetly", "Web Developer", "Male"]

Provalo on StackBlitz

Nel blocco di codice qui sopra, abbiamo usato il parametro rest ...otherInfo per mettere "CodeSweetly", "Web Developer" e "Male" in un array.

Ora, considera l'esempio con l'operatore spread:

// Definisci una funzione con tre parametri:
function myBio(firstName, lastName, company) { 
  return `${firstName} ${lastName} runs ${company}`;
}

// Usa spread per espandere gli elementi dell'array in argomenti individuali:
myBio(...["Oluwatobi", "Sofela", "CodeSweetly"]);

// L'invocazione qui sopra restituirà:
“Oluwatobi Sofela runs CodeSweetly”

Provalo su StackBlitz

In questo blocco di codice, abbiamo usato l'operatore spread (...) per espandere il contenuto dell'array ["Oluwatobi", "Sofela", "CodeSweetly"] sui parametri di myBio().

Non preoccuparti se non capisci ancora gli operatori rest e spread. Questo articolo è qui per te!

Nelle prossime sezioni discuteremo come funzionano in JavaScript i parametri rest e spread.

Quindi, senza cincischiare, iniziamo con l'operatore rest.

Cosa è esattamente l'operatore rest?

L'operatore rest è usato per mettere il resto di determinati valori inseriti dall'utente in un array JavaScript.

Ecco la sintassi dell'operatore rest:

...yourValues

I tre puntini (...) in questa riga di codice simbolizzano l'operatore rest.

Il testo dopo l'operatore si riferisce ai valori che vuoi mettere nell'array. Puoi usarlo solo prima dell'ultimo parametro nella definizione di una funzione.

Per capire la sintassi, vediamo come funziona l'operatore rest con le funzioni JavaScript.

Come agisce l'operatore rest in una funzione?

Nelle funzioni JavaScript, l'operatore rest viene usato come prefisso dell'ultimo parametro della funzione.

Ecco un esempio:

// Definisci una funzione con due parametri regolari e un parametro rest:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

L'operatore rest (...) dà istruzione al computer di aggiungere qualsiasi argomento otherInfo dato dall'utente dentro un array e assegna l'array al parametro otherInfo.

Quindi definiamo...otherInfo un parametro rest.

Note: gli argomenti sono valori opzionali che puoi passare ai parametri tramite un'invocazione.

Ecco un altro esempio:

// Definisci una funzione con due parametri regolari e un parametro rest:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

// Invoca la funzione myBio passando cinque argomenti ai suoi parametri:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");

// L'invocazione qui sopra restituirà:
["CodeSweetly", "Web Developer", "Male"]

Provalo su StackBlitz

Nello snippet che hai appena visto, nota che l'invocazione passa cinque argomenti alla funzione.

In altre parole, "Oluwatobi" e "Sofela" vengono assegnati ai parametri firstName e lastName.

Allo stesso tempo, l'operatore rest aggiunge gli argomenti rimanenti ("CodeSweetly", "Web Developer", e "Male") in un array assegnato al parametro otherInfo.

Quindi, la funzione myBio() restituisce correttamente ["CoreSweetly", "Web Developer", "Male"] come contenuto del parametro rest otherInfo.

Attenzione! Non puoi usare "use strict" in una funzione che contiene un parametro rest

Tieni a mente che non puoi usare la direttiva "use strict" dentro una funzione che contiene un parametro rest, parametri di default o parametri destrutturati, altrimenti, il computer darà un errore di sintassi.

Considera questo esempio:

// Definisci una funzione con un parametro rest:
function printMyName(...value) {
  "use strict";
  return value;
}

// La definizione qui sopra restituirà:
"Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list"

Provalo su CodeSandbox

La funzione printMyName() restituisce un errore di sintassi perché abbiamo usato la direttiva "use strict" dentro una funzione con un parametro rest.

Ma supponi di aver bisogno che la tua funzione sia in strict mode e allo stesso tempo usi il parametro rest. In un caso come questo, puoi scrivere la direttiva "use strict" al di fuori della funzione.

Ecco un esempio:

// Definisci la direttiva "use strict" al di fuori della funzione:
"use strict";

// Definisci una funzione con un parametro rest:
function printMyName(...value) {
  return value;
}

// Invoca la funzione printMyName passando due argomenti alla funzione:
printMyName("Oluwatobi", "Sofela");

// L'invocazione qui sopra restituirà:
["Oluwatobi", "Sofela"]

Provalo su CodeSandbox

Nota: metti la direttiva "use strict" al di fuori della funzione solo se va bene che tutto lo script o l'ambito esterno sia in strict mode.

Quindi adesso che sappiamo come agisce l'operatore rest in una funzione, possiamo parlare di come funziona in una assegnazione con destrutturazione.

Come funziona l'operatore rest in una assegnazione con destrutturazione

L'operatore rest viene usato tipicamente come prefisso dell'ultima variabile dell'assegnazione con destrutturazione.

Ecco un esempio:

// Definisci un array di destrutturazione con due variabili normali e una variabile rest:
const [firstName, lastName, ...otherInfo] = [
  "Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male"
];

// Invoca la variabile otherInfo:
console.log(otherInfo); 

// L'invocazione qui sopra restituisce:
["CodeSweetly", "Web Developer", "Male"]

Provalo su StackBlitz

L'operatore rest (...) dà istruzione al computer di aggiungere i valori rimanenti tra quelli assegnati dall'utente dentro un array. Poi, assegna l'array alla variabile otherInfo.

Quindi, possiamo chiamare ...otherInfo una variabile rest.

Ecco un altro esempio:

// Definisci un oggetto di destrutturazione con due variabili regolari e una variabile rest:
const { firstName, lastName, ...otherInfo } = {
  firstName: "Oluwatobi",
  lastName: "Sofela", 
  companyName: "CodeSweetly",
  profession: "Web Developer",
  gender: "Male"
}

// Invoca la variabile otherInfo:
console.log(otherInfo);

// L'invocazione qui sopra restituisce:
{companyName: "CodeSweetly", profession: "Web Developer", gender: "Male"}

Provalo su StackBlitz

Nel blocco di codice che hai appena visto, puoi notare che l'operatore rest assegna le proprietà di un oggetto, non un array, alla variabile otherInfo.

In altre parole, ogni volta che usi un oggetto di destrutturazione, l'operatore rest produce un oggetto con proprietà.

Invece, se usi rest in un array di destrutturazione o in una funzione, l'operatore restituisce un array letterale.

Prima di concludere la nostra discussione sull'operatore rest, dovresti essere a conoscenza di alcune differenze tra l'oggetto arguments e il parametro rest.

Qual è la differenza tra argomenti e parametri rest?

Ecco alcune delle differenze tra gli argomenti e il parametro rest:

Differenza 1: arguments è un oggetto simil-array, non un vero array

Tieni a mente che in JavaScript l'oggetto arguments non è un vero array. Invece è un oggetto simil-array che non ha tutte le funzionalità di un normale array JavaScript.

Il parametro rest, invece, è un vero oggetto array e puoi usare tutti i metodi degli array su di esso.

Quindi, puoi usare i metodi sort(), map(), forEach() o pop() su un parametro rest, ma non puoi fare lo stesso su un oggetto arguments.

Differenza 2: Non puoi usare l'oggetto arguments in una funzione freccia

L'oggetto arguments non è disponibile in una funzione freccia, quindi non puoi usarlo al suo interno, ma puoi usare il parametro rest in tutte le funzioni, incluse le funzioni freccia.

È meglio usare il parametro rest invece dell'oggetto arguments - specialmente quando scrivi codice compatibile con ES6.

Ora che sappiamo come funziona rest, parliamo dell'operatore spread così da vedere le differenze.

Cosa è l'operatore spread e come funziona in JavaScript?

L'operatore spread (...)  aiuta a espandere gli iterabili in elementi singoli.

La sintassi spread funziona dentro array letterali, invocazioni di funzioni e oggetti di proprietà inizializzati per spalmare i valori di oggetti iterabili in elementi separati. In pratica, fa l'opposto dell'operatore spread.

Nota: un operatore spread è efficace solo se usato dentro array letterali, invocazioni di funzioni o oggetti di proprietà inizializzati.

Cosa significa esattamente? Vediamolo con degli esempi.

Esempio 1: Come funziona spread in un array letterale

const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// L'invocazione qui sopra restituisce:
[ "Oluwatobi", "Sofela", "is", "my", "name." ]

Provalo su StackBlitz

Lo snippet di codice qui sopra usa l'operatore spread per copiare l'array myName dentro aboutMe.

Nota:

  • Alterazioni di myName non si rifletteranno su aboutMe perché i valori dentro myName sono primitivi. Quindi, l'operatore spread semplicemente copia e incolla il contenuto di myName dentro aboutMe senza creare nessun riferimento all'array originale.
  • L'operatore spread fa solo copie superficiali. Quindi tieni a mente che se supponiamo che myName contenga valori non primitivi, il computer creerebbe un riferimento tra myName e aboutMe. Vedi info 3 per più informazioni su come funziona l'operatore spread con i valori primitivi e non primitivi.
  • Supponi che non abbiamo usato la sintassi spread per duplicare il contenuto di myName. Per esempio, se avessimo scritto const aboutMe = ["Oluwatobi", myName, "name."]. In un caso come questo, il computer assegnerebbe un riferimento a myName. Quindi, qualsiasi cambiamento fatto all'array originale si riflette nel duplicato.

Esempio 2: Come usare spread per convertire una stringa in elementi individuali di un array

const myName = "Oluwatobi Sofela";

console.log([...myName]);

// L'invocazione qui sopra restituisce:
[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]

Provalo su StackBlitz

Nello snippet qui sopra, abbiamo usato la sintassi spread dentro un array letterale per espandere la stringa myName in elementi individuali.

Quindi, "Oluwatobi Sofela" è stato espanso in [ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ].

Esempio 3: come funziona l'operatore spread in una invocazione di funzione

const numbers = [1, 3, 5, 7];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// L'invocazione restituisce:
16

Provalo su StackBlitz

Abbiamo usato la sintassi spread per espandere l'array numbers sui parametri di addNumbers().

Supponi che l'array numbers abbia più di quattro elementi. In un caso come questo, il computer usa solo i primi quattro elementi come argomento di addNumbers() e ignora il resto.

Ecco un esempio:

const numbers = [1, 3, 5, 7, 10, 200, 90, 59];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// L'invocazione restituisce:
16

Provalo su StackBlitz

Ecco un altro esempio:

const myName = "Oluwatobi Sofela";

function spellName(a, b, c) {
  return a + b + c;
}

console.log(spellName(...myName));      // restituisce: "Olu"

console.log(spellName(...myName[3]));   // restituisce: "wundefinedundefined"

console.log(spellName([...myName]));    // restituisce: "O,l,u,w,a,t,o,b,i, ,S,o,f,e,l,aundefinedundefined"

console.log(spellName({...myName}));    // restituisce: "[object Object]undefinedundefined"

Provalo su StackBlitz

Esempio 4: come funziona spread in un oggetto letterale

const myNames = ["Oluwatobi", "Sofela"];
const bio = { ...myNames, runs: "codesweetly.com" };

console.log(bio);

// L'invocazione restituisce:
{ 0: "Oluwatobi", 1: "Sofela", runs: "codesweetly.com" }

Provalo su StackBlitz

In questo esempio, abbiamo usato spread dentro l'oggetto bio per espandere i valori di myNames in proprietà individuali.

Cose da sapere sull'operatore spread

Tieni a mente queste tre essenziali informazioni quando scegli di usare l'operatore spread.

Info 1: L'operatore spread non può espandere i valori letterali di un oggetto

Visto che un oggetto di proprietà non è un oggetto iterabile, non puoi usare l'operatore spread per espandere i suoi valori.

Però puoi usare l'operatore spread per clonare le proprietà da un oggetto a un altro.

Ecco un esempio:

const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName, website: "codesweetly.com" };

console.log(bio);

// L'invocazione restituisce
{ firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com" };

Provalo su Stack

Il codice che hai appena letto fa uso dell'operatore spread per clonare il contenuto di myName dentro l'oggetto bio.

Nota:

  • L'operatore spread può espandere solo i valori di oggetti iterabili.
  • Un oggetto è iterabile sono se ha (o qualsiasi oggetto nella sua catena prototype ha) una proprietà con una chiave @@iterator.
  • Array, TypedArray, String, Map, e Set sono tipi iterabili integrati perché hanno la proprietà @@iterator di default.
  • Un oggetto di proprietà non è un tipo di dato iterabile perché non ha la proprietà @@iterator di default.
  • Puoi rendere un oggetto di proprietà iterabile aggiungendogli la proprietà @@iterator.

Info 2: L'operatore spread non clona proprietà identiche

Supponi di avere usato l'operatore spread per clonare le proprietà dall'oggetto A all'oggetto B. E supponi che B contenga proprietà identiche a quelle nell'oggetto A. In tal caso, la versione di B ha precedenza su quella di A.

Ecco un esempio:

const myName = { firstName: "Tobi", lastName: "Sofela" };
const bio = { ...myName, firstName: "Oluwatobi", website: "codesweetly.com" };

console.log(bio);

// L'invocazione restituisce:
{ firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com" };

Provalo su StackBlitz

Osserva che l'operatore spread non ha copiato la proprietà firstName da myName dentro l'oggetto bio perché bio già contiene una proprietà firstName.

Info 3: attenzione a come funziona spread quando usato su oggetti che contengono valori non primitivi!

Supponi che hai usato l'operatore spread su un oggetto (o array) contenente solo valori primitivi. Il computer non creerà nessun riferimento tra l'oggetto originale e quello duplicato.

Per esempio, considera questo codice:

const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// L'invocazione restituisce:
["Oluwatobi", "Sofela", "is", "my", "name."]

Provalo su StackBlitz

Osserva che ogni elemento in myName è un valore primitivo. Quindi, quando usiamo l'operatore spread per clonare myName in aboutMe, il computer non ha creato un riferimento tra questi due array.

Qualsiasi modifica fai a myName non si riflette su aboutMe e vice versa.

Come esempio, aggiungiamo un elemento in più a myName:

myName.push("real");

Ora, controlliamo lo stato corrente di myName e aboutMe.

console.log(myName); // ["Sofela", "is", "my", "real"]

console.log(aboutMe); // ["Oluwatobi", "Sofela", "is", "my", "name."]

Provalo su StackBlitz

Il contenuto aggiornato di myName non è rispecchiato in aboutMe, perché spread non ha creato nessun riferimento tra l'array originale e quello duplicato.

Cosa succede se myName contiene elementi non primitivi?

Supponi che myName contenga elementi non primitivi. In tal caso, spread crea un riferimento tra l'originale non primitivo e quello clonato.

Ecco un esempio:

const myName = [["Sofela", "is", "my"]];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// L'invocazione restituisce:
[ "Oluwatobi", ["Sofela", "is", "my"], "name." ]

Provalo su StackBlitz

Osserva che myName contiene un valore non primitivo.

Quindi, usare l'operatore spread per clonare il contenuto di myName in aboutMe fa sì che il computer crei un riferimento tra i due array.

Qualsiasi alterazione che fai alla copia di myName si rifletterà sulla versione di aboutMe e viceversa.

Come esempio, aggiungiamo contenuto ulteriore a myName:

myName[0].push("real");

Ora, controlliamo lo stato attuale di myName e di aboutMe:

console.log(myName); // [["Sofela", "is", "my", "real"]]

console.log(aboutMe); // ["Oluwatobi", ["Sofela", "is", "my", "real"], "name."]

Provalo su StackBlitz

Il contenuto aggiornato di myName si riflette in aboutMe, perché l'operatore spread ha creato un riferimento tra l'array originale e quello duplicato.

Ecco un altro esempio:

const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName };

myName.firstName = "Tobi";

console.log(myName); // { firstName: "Tobi", lastName: "Sofela" }

console.log(bio); // { firstName: "Oluwatobi", lastName: "Sofela" }

Provalo su StackBlitz

In quest'esempio, l'aggiornamento di myName non si è riflettuto in bio perché abbiamo usato l'operatore spread su un oggetto che contiene solo valori primitivi.

Nota: Uno sviluppatore direbbe che myName è un oggetto mono-dimensionale perché contiene solo elementi primitivi.

Ecco un altro esempio:

const myName = { 
  fullName: { firstName: "Oluwatobi", lastName: "Sofela" }
};

const bio = { ...myName };

myName.fullName.firstName = "Tobi";

console.log(myName); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }

console.log(bio); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }

Provalo su StackBlitz

In questo caso, l'aggiornamento di myName si riflette in bio perché usiamo l'operatore spread su un oggetto che contiene un valore non primitivo.

Note:

  • Chiamiamo myName oggetto profondo perché contiene elementi non primitivi.
  • Quando cloni un oggetto in un altro, fai una copia superficiale. Per esempio, ...myName produce una copia superficiale dell'oggetto perché qualsiasi alterazione di un oggetto si riflette sull'altro.
  • Quando cloni oggetti senza creare riferimenti, fai un copia profonda . Ad esempio, potrei copiare myName in bio facendo const bio = JSON.parse(JSON.stringify(myName)). In questo modo, il computer clonerà myName in bio senza creare riferimenti.
  • Puoi interrompere il riferimento tra due oggetti sostituendo l'oggetto fullName all'interno di myName o bio con un nuovo oggetto. Ad esempio, con myName.fullName = { firstName: "Tobi", lastName: "Sofela" } interromperai il pointer tra myName e bio.

Per concludere

In quest'articolo, abbiamo parlato delle differenze tra gli operatori rest e spread, e grazie a degli esempi abbiamo visto come funzionano entrambi.

Grazie per aver letto quest'articolo!