Due collezioni oltre a oggetti e array
Oggetti e array semplici coprono gran parte di ciò che serve ai programmi JavaScript, ma non sono stati progettati per ogni compito. Map e Set sono collezioni integrate che colmano due lacune specifiche: ricerche per chiave quando le chiavi non sono stringhe, e controlli di appartenenza senza duplicati.
Fanno parte del linguaggio da ES2015. Sono entrambe iterabili, hanno entrambe una proprietà .size e funzionano bene con l'operatore spread. Il modello mentale è semplice:
Map: come un oggetto, ma le chiavi possono essere qualsiasi cosa e l'ordine viene preservato.Set: come un array, ma i valori sono unici e la ricerca è veloce.
Creare e usare una Map
Una Map contiene coppie chiave/valore. La crei con new Map() e usi .set(), .get(), .has() e .delete():
Puoi anche passare al costruttore un array di coppie [key, value] per popolarla:
Questa forma ad array di due elementi compare ovunque ci siano di mezzo le Map: è il modo in cui le voci vengono rappresentate quando iteri.
Map contro oggetto: perché preoccuparsene?
Gli oggetti semplici sembrano fare lo stesso lavoro. Il più delle volte è così. Ma le Map sistemano alcuni spigoli specifici:
Gli oggetti ereditano da Object.prototype, quindi chiavi come toString, constructor e hasOwnProperty esistono già su ogni oggetto. Le Map non hanno questo bagaglio: le chiavi che imposti sono le uniche che esistono.
Le altre differenze da conoscere:
- Qualsiasi tipo di chiave. Le Map accettano come chiavi oggetti, funzioni, numeri, booleani. Gli oggetti convertono in silenzio le chiavi non stringa in stringhe:
obj[1]eobj["1"]sono la stessa posizione. - Ordine di inserimento garantito. Le Map iterano nell'ordine in cui le voci sono state aggiunte. Anche gli oggetti lo fanno quasi sempre, ma le chiavi stringa che sembrano numeri vengono ordinate per prime: una trappola sottile.
- Dimensione integrata.
map.sizeè O(1). Per un oggetto dovresti scrivereObject.keys(obj).length, che ricostruisce un array. - Ottimizzate per i cambiamenti frequenti. I motori ottimizzano le Map per aggiunte e rimozioni frequenti. Gli oggetti sono ottimizzati per record dalla forma stabile.
Usa un oggetto quando modelli un record con chiavi stringa note ({ name, email, age }). Usa una Map quando le chiavi sono dinamiche, non sono stringhe, oppure quando aggiungerai e rimuoverai voci spesso.
Scorrere una Map
Le Map sono iterabili, quindi for...of funziona direttamente e destrutturare ogni voce viene naturale:
Se vuoi solo le chiavi o solo i valori, chiama .keys() o .values(). E c'è anche .forEach(), se lo preferisci:
Per trasformare di nuovo una Map in un oggetto semplice o in un array, usa lo spread:
Creare e usare un Set
Un Set contiene valori unici. Aggiungere un valore già presente non ha alcun effetto:
L'unicità viene stabilita con la stessa regola di uguaglianza di ===, con una stranezza: dentro un Set NaN è considerato uguale a se stesso, anche se ovunque altro NaN === NaN vale false.
Passa un iterabile al costruttore per popolare un Set: è da qui che nasce il trucco per eliminare i duplicati.
Una riga, qualsiasi tipo primitivo. Con gli array di oggetti non funziona (due oggetti diversi con gli stessi campi restano due valori diversi), ma per stringhe, numeri e booleani è il modo idiomatico di eliminare i duplicati.
Set contro array: quando cambiare
Array e Set contengono entrambi una collezione di valori, quindi quando scegliere l'uno o l'altro?
Scegli un Set quando:
- I valori devono essere unici e vuoi che sia il runtime a garantirlo.
- Fai molti controlli di appartenenza.
set.has(x)è O(1);array.includes(x)è O(n). Dentro un ciclo, questa differenza si accumula in fretta. - Ti basta l'ordine di inserimento. I Set iterano in ordine di inserimento ma non supportano l'accesso per indice.
Resta sugli array quando:
- Ti serve l'accesso per posizione:
arr[0], slicing, ordinamento. - I duplicati hanno un significato: un carrello con due articoli uguali.
- Userai molto i metodi degli array come
.map,.filter,.reduce. I Set non li hanno; dovresti prima convertirli in array con lo spread.
Un rapido esempio orientato alle prestazioni:
Se banned fosse un array, ogni callback di filter scorrerebbe l'intera lista. Come Set, ogni ricerca richiede tempo costante.
Scorrere un Set
Stessa storia delle Map: for...of funziona e basta, e lo spread ti dà un array.
I Set espongono anche .keys(), .values() e .entries() per simmetria con le Map, anche se per un Set chiavi e valori sono la stessa cosa. Nella maggior parte dei casi itererai direttamente.
Un esempio completo: contare i visitatori unici per pagina
Combiniamo le due cose: una Map che associa i percorsi delle pagine a un Set di ID dei visitatori.
La Map gestisce l'associazione tra percorso e contenitore; il Set gestisce l'eliminazione dei duplicati dentro ogni contenitore. Fare lo stesso con un oggetto semplice e degli array funzionerebbe, ma dovresti scrivere controlli indexOf e guardie hasOwnProperty in più per tutto il tempo.
WeakMap e WeakSet, in breve
Esistono due collezioni collegate per un caso d'uso ristretto: WeakMap e WeakSet. Mantengono riferimenti deboli, cioè una voce la cui chiave (per WeakMap) o il cui valore (per WeakSet) non ha altri riferimenti viene eliminata automaticamente dal garbage collector.
Accettano solo oggetti come chiavi, non sono iterabili e non hanno .size. È voluto: se potessi iterarle, il lavoro del garbage collector diventerebbe osservabile. Sono utili per memorizzare metadati su oggetti che non ti appartengono, e rare nel codice di tutti i giorni.
Prossimo passo: JSON
Map e Set sono ottime in memoria, ma nessuna delle due sopravvive intatta a JSON.stringify: le Map diventano {} e anche i Set diventano {}. La prossima pagina parla di JSON: come serializzare e fare il parsing dei dati, e gli schemi per gestire le collezioni introdotte qui quando devono attraversare una rete o un file.
Domande frequenti
Qual è la differenza tra una Map e un oggetto in JavaScript?
Una Map può usare qualsiasi valore come chiave (oggetti, funzioni, numeri, qualunque cosa), mentre un oggetto converte le chiavi in stringhe (o simboli). Map tiene anche traccia della propria dimensione con .size, itera in ordine di inserimento e non eredita chiavi da un prototipo, quindi non c'è rischio di conflitti con toString o constructor. Scegli Map quando le chiavi non sono stringhe o quando aggiungi e rimuovi voci di frequente.
A cosa serve un Set in JavaScript?
Un Set memorizza valori unici: ignora in silenzio i duplicati. Il modo più veloce per eliminare i duplicati da un array è [...new Set(arr)]. I Set offrono anche controlli .has() in O(1), più veloci di array.includes() quando verifichi l'appartenenza dentro un ciclo.
Come si scorre una Map?
for...of funziona direttamente: for (const [key, value] of myMap) destruttura ogni voce. Puoi anche scorrere myMap.keys(), myMap.values() o myMap.entries(). L'ordine di iterazione è garantito uguale all'ordine di inserimento, cosa che gli oggetti semplici non sempre promettono per le chiavi che sembrano numeri.