Mappa vs oggetto in JavaScript


290

Ho appena scoperto chromestatus.com e, dopo aver perso diverse ore della mia giornata, ho trovato questa funzione :

Mappa: gli oggetti mappa sono semplici mappe chiave / valore.

Mi ha confuso. Gli oggetti JavaScript normali sono dizionari, quindi in che modo Mapdifferisce da un dizionario? Concettualmente, sono identici (secondo Qual è la differenza tra una mappa e un dizionario? )

I riferimenti alla documentazione chromestatus non aiutano neanche:

Gli oggetti mappa sono raccolte di coppie chiave / valore in cui sia le chiavi che i valori possono essere valori di linguaggio ECMAScript arbitrari. Un valore chiave distinto può verificarsi solo in una coppia chiave / valore all'interno della raccolta della mappa. Valori chiave distinti come discriminati utilizzando un algoritmo di confronto che viene selezionato al momento della creazione della mappa.

Un oggetto Mappa può iterare i suoi elementi in ordine di inserimento. L'oggetto mappa deve essere implementato utilizzando tabelle hash o altri meccanismi che, in media, forniscono tempi di accesso che sono sublineari sul numero di elementi nella raccolta. Le strutture di dati utilizzate in questa specifica degli oggetti della mappa hanno lo scopo solo di descrivere la semantica osservabile richiesta degli oggetti della mappa. Non intende essere un modello di implementazione fattibile.

... mi sembra ancora un oggetto, quindi chiaramente mi sono perso qualcosa.

Perché JavaScript sta guadagnando un oggetto (ben supportato) Map? Che cosa fa?


Risposte:


286

Secondo Mozilla:

Un oggetto Map può iterare i suoi elementi in ordine di inserimento - un ciclo for..of restituirà una matrice di [chiave, valore] per ogni iterazione.

e

Gli oggetti sono simili a Maps in quanto entrambi consentono di impostare chiavi su valori, recuperare tali valori, eliminare chiavi e rilevare se qualcosa è memorizzato in una chiave. Per questo motivo, gli oggetti sono stati usati storicamente come mappe; tuttavia, ci sono differenze importanti tra Oggetti e Mappe che rendono migliore l'uso di una Mappa.

Un oggetto ha un prototipo, quindi ci sono chiavi predefinite nella mappa. Tuttavia, questo può essere ignorato usando map = Object.create (null). Le chiavi di un oggetto sono stringhe, dove possono avere qualsiasi valore per una mappa. Puoi ottenere facilmente le dimensioni di una mappa mentre devi tenere traccia manualmente delle dimensioni di un oggetto.

Usa le mappe sugli oggetti quando le chiavi sono sconosciute fino al momento dell'esecuzione e quando tutte le chiavi sono dello stesso tipo e tutti i valori sono dello stesso tipo.

Utilizzare gli oggetti quando esiste una logica che opera su singoli elementi.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

L'iterabilità nell'ordine è una funzionalità che è stata a lungo richiesta dagli sviluppatori, in parte perché garantisce le stesse prestazioni in tutti i browser. Quindi per me è grande.

Il myMap.has(key)metodo sarà particolarmente utile e anche la myMap.sizeproprietà.


13
Un aspetto negativo, presumibilmente, è che una mappa richiede più memoria (all'interno dello stesso ordine di grandezza, tuttavia) per mantenere l'ordine di inserimento.
John Kurlak,

4
Le mappe hanno altre caratteristiche oltre all'ordinamento che sono state menzionate qui (usando qualsiasi oggetto come chiave, separazione di chiavi e oggetti di scena, ecc.), Ma in alcuni casi FWIW l'ordine di iterazione delle proprietà dell'oggetto normale è definito da ES2015. Vedi stackoverflow.com/a/32149345 .
JMM,

2
Non ho capito il significato, quando dici: Un oggetto ha un prototipo, quindi ci sono chiavi predefinite nella mappa. Tuttavia, questo può essere aggirato usandomap = Object.create(null) . Quali sono le chiavi predefinite? Come sono correlate le chiavi Object.prototype?
scambio eccessivo il

4
I miei test su Chrome hanno dimostrato che le mappe non utilizzano quantità significative di memoria in più per mantenere l'ordine. Penso che ci fossero 0,1 KB in più per un milione di chiavi e non penso che fosse per mantenere l'ordine. Tuttavia, quel ~ 0.1KB sembra essere un overhead costante. Se invece crei un milione di mappe con una chiave e la confronti è molto più grande dell'oggetto.
jgmjgm,

2
@luxon stai creando un oggetto lì. Le specifiche ES6 richiedono che l' newoperatore venga utilizzato con il Mapsimbolo, ad esempio new Mapper creare un oggetto mappa. var a = {}è una scorciatoia per (che significa equivalente a)var a = Object.create(Object.prototype)
dudewad il

104

La differenza fondamentale è che gli oggetti supportano solo chiavi di stringa in cui le mappe supportano più o meno qualsiasi tipo di chiave.

Se lo faccio obj[123] = truee poi Object.keys(obj)otterrò ["123"]piuttosto che [123]. Una mappa manterrebbe il tipo di chiave e restituirà il [123]che è fantastico. Le mappe consentono inoltre di utilizzare gli oggetti come chiavi. Tradizionalmente per fare questo dovresti dare agli oggetti una sorta di identificatore univoco per cancellarli (non credo di aver mai visto qualcosa di simile getObjectIdin JS come parte dello standard). Le mappe garantiscono anche la conservazione dell'ordine, quindi sono sempre meglio per la conservazione e talvolta possono farti risparmiare la necessità di fare alcuni tipi.

Tra mappe e oggetti in pratica ci sono diversi pro e contro. Gli oggetti ottengono sia vantaggi che svantaggi essendo strettamente integrati nel nucleo di JavaScript, il che li distingue in modo significativo da Map oltre la differenza nel supporto chiave.

Un vantaggio immediato è che hai il supporto sintattico per gli oggetti che semplifica l'accesso agli elementi. Hai anche il supporto diretto per esso con JSON. Se usato come hash è fastidioso ottenere un oggetto senza alcuna proprietà. Per impostazione predefinita, se si desidera utilizzare gli oggetti come tabella hash, questi verranno inquinati e spesso sarà necessario richiamarli hasOwnPropertyquando si accede alle proprietà. Puoi vedere qui come per impostazione predefinita gli oggetti sono inquinati e come creare oggetti sperabilmente non inquinati da usare come hash:

({}).toString
    toString() { [native code] }
JSON.parse('{}').toString
    toString() { [native code] }
(Object.create(null)).toString
    undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
    undefined

L'inquinamento sugli oggetti non è solo qualcosa che rende il codice più fastidioso, più lento, ecc., Ma può anche avere potenziali conseguenze per la sicurezza.

Gli oggetti non sono pure tabelle hash ma stanno provando a fare di più. Hai mal di testa come hasOwnProperty, non essere in grado di ottenere facilmente la lunghezza ( Object.keys(obj).length) e così via. Gli oggetti non sono pensati per essere usati esclusivamente come mappe hash ma anche come oggetti dinamici estensibili e quindi quando li usi come pure tabelle hash si presentano problemi.

Confronto / Elenco di varie operazioni comuni:

    Object:
       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       if(o.hasOwnProperty('key'));
       delete(o.key);
       Object.keys(o).length
    Map:
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;
       if(m.has('key'));
       m.delete('key');
       m.size();

Ci sono alcune altre opzioni, approcci, metodologie, ecc. Con alti e bassi variabili (prestazioni, terse, portatili, estensibili, ecc.). Gli oggetti sono un po 'strani essendo fondamentali nel linguaggio, quindi hai molti metodi statici per lavorare con loro.

Oltre al vantaggio di Mappe di preservare i tipi di chiave e di essere in grado di supportare oggetti come gli oggetti, sono isolati dagli effetti collaterali che gli oggetti hanno. Una mappa è un puro hash, non c'è confusione nel cercare di essere un oggetto allo stesso tempo. Le mappe possono anche essere facilmente estese con le funzioni proxy. Gli oggetti attualmente hanno una classe Proxy, tuttavia le prestazioni e l'utilizzo della memoria sono tristi, infatti la creazione del proprio proxy che assomiglia a Map for Objects attualmente funziona meglio del Proxy.

Uno svantaggio sostanziale per Maps è che non sono supportati direttamente con JSON. L'analisi è possibile ma ha diversi blocchi:

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;
});

Quanto sopra introdurrà un grave impatto sulle prestazioni e non supporterà alcuna chiave di stringa. La codifica JSON è ancora più difficile e problematica (questo è uno dei tanti approcci):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())
    });
};

Questo non è così male se stai semplicemente usando Maps ma avrai problemi quando mescoli tipi o usi valori non scalari come chiavi (non che JSON sia perfetto con quel tipo di problema com'è, riferimento circolare agli oggetti IE). Non l'ho testato, ma è probabile che danneggerà gravemente le prestazioni rispetto a stringere.

Altri linguaggi di scripting spesso non presentano tali problemi in quanto hanno tipi espliciti non scalari per Map, Object e Array. Lo sviluppo Web è spesso una seccatura con tipi non scalari in cui devi fare i conti con cose come PHP unisce Array / Map con Object usando A / M per le proprietà e JS unisce Map / Object con Array che estende M / O. La fusione di tipi complessi è la rovina del diavolo di linguaggi di scripting di alto livello.

Finora si tratta in gran parte di problemi relativi all'implementazione, ma anche le prestazioni per le operazioni di base sono importanti. Le prestazioni sono anche complesse perché dipendono dal motore e dall'utilizzo. Fai i miei test con un granello di sale perché non posso escludere alcun errore (devo affrettarmi). Dovresti anche eseguire i tuoi test per confermare mentre il mio esamina solo scenari semplici molto specifici per dare solo un'indicazione approssimativa. Secondo i test in Chrome per oggetti / mappe molto grandi, le prestazioni degli oggetti sono peggiori a causa dell'eliminazione che è apparentemente in qualche modo proporzionata al numero di chiavi anziché a O (1):

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome ha chiaramente un grande vantaggio nell'ottenere e aggiornare, ma le prestazioni di eliminazione sono orribili. In questo caso, le mappe utilizzano una quantità minima di memoria (overhead) ma con un solo oggetto / mappa testato con milioni di chiavi, l'impatto dell'overhead per le mappe non viene espresso bene. Con la gestione della memoria anche gli oggetti sembrano liberarsi prima se leggo correttamente il profilo, il che potrebbe essere un vantaggio a favore degli oggetti.

In Firefox per questo particolare benchmark è una storia diversa:

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

Devo immediatamente sottolineare che in questo particolare benchmark l'eliminazione di oggetti in Firefox non sta causando alcun problema, tuttavia in altri benchmark ha causato problemi soprattutto quando ci sono molti tasti proprio come in Chrome. Le mappe sono chiaramente superiori in Firefox per raccolte di grandi dimensioni.

Tuttavia questa non è la fine della storia, che dire di molti piccoli oggetti o mappe? Ho fatto un rapido benchmark di questo, ma non esaustivo (impostazione / acquisizione) dei quali funziona meglio con un piccolo numero di tasti nelle operazioni sopra. Questo test riguarda più la memoria e l'inizializzazione.

Map Create: 69    // new Map
Object Create: 34 // {}

Anche in questo caso queste cifre variano, ma sostanzialmente Object ha un buon vantaggio. In alcuni casi il vantaggio di Oggetti su mappe è estremo (~ 10 volte meglio) ma in media era circa 2-3 volte migliore. Sembra che picchi di prestazioni estreme possano funzionare in entrambi i modi. L'ho testato solo in Chrome e nella creazione per profilare l'utilizzo della memoria e il sovraccarico. Sono stato piuttosto sorpreso di vedere che in Chrome sembra che Maps con una chiave utilizzi circa 30 volte più memoria degli Oggetti con una chiave.

Per testare molti piccoli oggetti con tutte le operazioni di cui sopra (4 tasti):

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

In termini di allocazione della memoria, questi si comportarono allo stesso modo in termini di liberazione / GC, ma Map usò 5 volte più memoria. Questo test ha utilizzato 4 tasti in cui, come nell'ultimo test, ho impostato solo un tasto in modo da spiegare la riduzione del sovraccarico di memoria. Ho eseguito questo test alcune volte e Map / Object sono più o meno in generale per Chrome in termini di velocità complessiva. In Firefox per piccoli oggetti c'è un netto vantaggio prestazionale rispetto alle mappe in generale.

Questo ovviamente non include le singole opzioni che potrebbero variare in modo selvaggio. Non consiglierei la microottimizzazione con queste cifre. Ciò che puoi ottenere da questo è che come regola empirica, considera Maps più fortemente per archivi di valori chiave molto grandi e oggetti per archivi di valori chiave piccoli.

Oltre a ciò la migliore strategia con questi due è di implementarlo e farlo funzionare prima. Quando si profila, è importante tenere presente che a volte le cose che non si penserebbero lente quando le si guarda possono essere incredibilmente lente a causa delle stranezze del motore, come si vede con il caso di cancellazione della chiave dell'oggetto.


La mancanza di serializzazione è stata una vera seccatura per molti sviluppatori. Guarda l'upgrade di Come posso persistere una mappa ES6 nell'archivio locale (o altrove)? e come si JSON.stringify una mappa ES6? .
Franklin Yu,

Il numero è in millisecondi, byte o oggetti totali?
StefansArya

Ha preso così ms (qualcosa è bastato per dire qualcosa di usato, quindi in questo caso impiega tempo). Anche se questo è un vecchio test e non ho più il codice di riferimento. Probabilmente è molto diverso ora. Il problema dell'eliminazione, ad esempio, credo sia stato risolto.
jgmjgm,

27

Non credo che i seguenti punti siano stati menzionati nelle risposte finora e ho pensato che valessero la pena menzionarli.


Le mappe possono essere più grandi

In Chrome posso ottenere 16,7 milioni di coppie chiave / valore con Mapcontro 11,1 milioni con un oggetto normale. Quasi esattamente il 50% in più di coppie con a Map. Entrambi occupano circa 2 GB di memoria prima del crash, e quindi penso che abbia a che fare con la limitazione della memoria da parte di Chrome ( Modifica : Sì, prova a riempire 2 Mapse ottieni solo 8,3 milioni di paia ciascuno prima che si blocchi). Puoi testarlo tu stesso con questo codice (eseguili separatamente e non allo stesso tempo, ovviamente):

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

Gli oggetti hanno già alcune proprietà / chiavi

Questo mi ha inciampato prima. Oggetti normali hanno toString, constructor, valueOf, hasOwnProperty, isPrototypeOfe un mucchio di altre proprietà preesistenti. Questo potrebbe non essere un grosso problema per la maggior parte dei casi d'uso, ma in precedenza mi ha causato problemi.

Le mappe possono essere più lente:

A causa del .getsovraccarico della chiamata di funzione e della mancanza di ottimizzazione interna, Map può essere notevolmente più lento di un semplice vecchio oggetto JavaScript per alcune attività.


1
Secondo te, la semantica supera le prestazioni qui? Le mappe sembrano perfette se hai bisogno di un dizionario, ma è difficile accettare una ricerca più lenta. Una ricerca veloce non è l'intero punto dei dizionari?
user2954463,

3
Ci tornerei sicuramente con vecchi oggetti semplici se si sta bene con 11 milioni di coppie chiave / valore e non si preoccupano i tasti pre-esistenti, come toString, constructorecc (cioè le chiavi sono estremamente improbabile che si scontrano con loro). Sono più facili da lavorare, ad esempio l'incremento è obj[i] = (obj[i] || 0) + 1, mentre con Mapesso map.set(i, (map.get(i) || 0) + 1)non è ancora così male, ma mostra solo come le cose possono diventare inutilmente disordinate. Le mappe hanno sicuramente i loro casi d'uso, ma spesso lo farà un semplice oggetto.

Si noti che si può sbarazzarsi di quello predefinito toString, constructor, (ecc) le proprietà degli oggetti, scrivendo obj = Object.create(null)al posto di obj = {}.

17

Gli oggetti possono comportarsi come dizionari perché Javascript viene digitato in modo dinamico, consentendo di aggiungere o rimuovere proprietà su un oggetto in qualsiasi momento.

Ma la nuova Map()funzionalità è molto migliore perché:

  • Esso prevede get, set, has, e deletemetodi.
  • Accetta qualsiasi tipo di chiave anziché solo stringhe.
  • Fornisce un iteratore per un facile for-ofutilizzo e mantiene l'ordine dei risultati.
  • Non ha casi limite con prototipi e altre proprietà che appaiono durante l'iterazione o la copia.
  • Supporta milioni di articoli.
  • È molto veloce e continua a diventare più veloce man mano che i motori javascript migliorano.

Il 99% delle volte dovresti semplicemente usare a Map(). Tuttavia, se stai utilizzando solo chiavi basate su stringhe e hai bisogno della massima prestazione di lettura, gli oggetti possono essere una scelta migliore.

Il dettaglio è che (quasi tutti) i motori javascript compilano oggetti fino a classi C ++ in background. Questi tipi vengono memorizzati nella cache e riutilizzati dal loro "contorno", quindi quando si crea un nuovo oggetto con le stesse proprietà esatte, il motore riutilizzerà una classe di sfondo esistente. Il percorso di accesso per le proprietà di queste classi è molto ottimizzato e molto più veloce della ricerca di a Map().

L'aggiunta o la rimozione di una proprietà causa la ricompilazione della classe di supporto memorizzata nella cache, motivo per cui l'utilizzo di un oggetto come dizionario con molte aggiunte e cancellazioni di chiavi è molto lento, ma le letture e l'assegnazione di chiavi esistenti senza modificare l'oggetto sono molto veloci.

Quindi, se si dispone di un carico di lavoro pesante per la lettura in scrittura con chiavi di stringa, utilizzare un objectdizionario specializzato ad alte prestazioni, ma per tutto il resto utilizzare un Map().


L'oggetto offre get set has deleteanche funzionalità ecc., Non è così elegante (ma neanche male). In che modo è Mappiù facile da usare per iterare? Non sono sicuro di essere d'accordo.
Andrew,

@Andrew Sto prendendo in considerazione i metodi e la funzionalità è diversa a seconda di ciò che stai usando e del risultato. L'iterazione è più semplice perché il prototipo e le proprietà native non vengono visualizzate nel ciclo e utilizza un normale iteratore JS che mantiene lo stesso ordine.
Mani Gandham

11

Oltre alle altre risposte, ho scoperto che Maps è più ingombrante e dettagliato con cui operare rispetto agli oggetti.

obj[key] += x
// vs.
map.set(map.get(key) + x)

Questo è importante perché il codice più breve è più veloce da leggere, più direttamente espressivo e meglio tenuto nella testa del programmatore .

Un altro aspetto: poiché set () restituisce la mappa, non il valore, è impossibile assegnare le catene.

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

Il debug delle mappe è anche più doloroso. Di seguito, non puoi effettivamente vedere quali chiavi sono presenti nella mappa. Dovresti scrivere codice per farlo.

Buona fortuna a valutare un Iteratore di mappe

Gli oggetti possono essere valutati da qualsiasi IDE:

WebStorm che valuta un oggetto


4
Alla luce di tutto ciò, sembra che la mappa sia un'ottimizzazione prematura.
PRMan,

10

Sommario:

  • Object: Una struttura di dati in cui i dati sono memorizzati come coppie chiave-valore. In un oggetto la chiave deve essere un numero, una stringa o un simbolo. Il valore può essere qualsiasi cosa, quindi anche altri oggetti, funzioni ecc. Un oggetto è una struttura di dati non ordinata , ovvero la sequenza di inserimento delle coppie chiave-valore non viene ricordata
  • ES6 Map: Una struttura di dati in cui i dati sono memorizzati come coppie chiave-valore. In cui una chiave univoca si associa a un valore . Sia la chiave che il valore possono essere in qualsiasi tipo di dati . Una mappa è una struttura di dati iterabile, ciò significa che viene ricordata la sequenza di inserimento e che possiamo accedere agli elementi in ad esempio un for..ofciclo

Differenze chiave:

  • A Mapè ordinato e ripetibile, mentre un oggetto non è ordinato e non ripetibile

  • Possiamo inserire qualsiasi tipo di dati come Mapchiave, mentre gli oggetti possono avere solo un numero, una stringa o un simbolo come chiave.

  • A Maperedita da Map.prototype. Questo offre tutti i tipi di funzioni e proprietà di utilità che rendono Mapmolto più semplice lavorare con gli oggetti.

Esempio:

oggetto:

let obj = {};

// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;

// getting nr of properties of the object
console.log(Object.keys(obj).length)

// deleting a property
delete obj[2]

console.log(obj)

Carta geografica:

const myMap = new Map();

const keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"

console.log(myMap.get('a string'));   // "value associated with 'a string'"
                         // because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

fonte: MDN


4

Oltre ad essere iterabile in un ordine ben definito e alla capacità di usare valori arbitrari come chiavi (eccetto -0), le mappe possono essere utili per i seguenti motivi:

  • Le specifiche impongono che le operazioni della mappa siano in media sublineari.

    Qualsiasi implementazione non stupida di oggetti utilizzerà una tabella hash o simile, quindi le ricerche di proprietà saranno probabilmente costanti in media. Quindi gli oggetti potrebbero essere persino più veloci delle mappe. Ma ciò non è richiesto dalle specifiche.

  • Gli oggetti possono avere cattivi comportamenti inaspettati.

    Ad esempio, supponiamo che tu non abbia impostato alcuna fooproprietà su un oggetto appena creato obj, quindi ti aspetti obj.foodi restituire indefinito. Ma foopotrebbe essere una proprietà incorporata ereditata da Object.prototype. Oppure si tenta di creare obj.fooutilizzando un compito, ma alcuni setter Object.prototypeeseguono invece di memorizzare il proprio valore.

    Le mappe impediscono questo tipo di cose. Bene, a meno che alcuni script non facciano casino Map.prototype. E Object.create(null)funzionerebbe anche, ma poi perdi la sintassi dell'inizializzatore di oggetti semplice.


4

Quando utilizzare Maps anziché semplici oggetti JavaScript?

Il semplice oggetto JavaScript {chiave: 'valore'} contiene dati strutturati. Ma il semplice oggetto JS ha i suoi limiti:

  1. Solo le stringhe e i simboli possono essere usati come chiavi degli oggetti. Se usiamo qualsiasi altra cosa diciamo, numeri come chiavi di un oggetto, quindi durante l'accesso a quelle chiavi vedremo che quelle chiavi verranno convertite in stringhe implicitamente facendoci perdere la coerenza dei tipi. const names = {1: 'one', 2: 'two'}; Object.keys (nomi); // ['1', '2']

  2. Esistono possibilità di sovrascrivere accidentalmente proprietà ereditate dai prototipi scrivendo identificatori JS come nomi chiave di un oggetto (ad es. ToString, costruttore ecc.)

  3. Un altro oggetto non può essere usato come chiave di un oggetto, quindi nessuna informazione aggiuntiva può essere scritta per un oggetto scrivendo quell'oggetto come chiave di un altro oggetto e il valore di quell'altro oggetto conterrà le informazioni extra

  4. Gli oggetti non sono iteratori

  5. La dimensione di un oggetto non può essere determinata direttamente

Queste limitazioni di Oggetti sono risolte da Maps ma dobbiamo considerare Maps come complemento di Oggetti anziché sostituzione. Fondamentalmente Mappa è solo una matrice di matrici ma dobbiamo passare quella matrice di matrici all'oggetto Mappa come argomento con una nuova parola chiave, altrimenti solo per la matrice di matrici le proprietà e i metodi utili di Mappa non sono disponibili. E ricorda che le coppie chiave-valore all'interno della matrice di matrici o la Mappa devono essere separate solo da virgole, senza due punti come negli oggetti normali.

3 suggerimenti per decidere se utilizzare una mappa o un oggetto:

  1. Usa le mappe sugli oggetti quando le chiavi sono sconosciute fino al runtime perché le chiavi formate dall'input dell'utente o inconsapevolmente possono rompere il codice che utilizza l'oggetto se tali chiavi sovrascrivono le proprietà ereditate dell'oggetto, quindi la mappa è più sicura in quei casi. Utilizza anche le mappe quando tutte le chiavi sono dello stesso tipo e tutte le mappe sono dello stesso tipo.

  2. Utilizzare le mappe se è necessario memorizzare i valori primitivi come chiavi.

  3. Usa gli oggetti se dobbiamo operare su singoli elementi.

I vantaggi dell'utilizzo di Maps sono:

1. La mappa accetta qualsiasi tipo di chiave e conserva il tipo di chiave:

Sappiamo che se la chiave dell'oggetto non è una stringa o un simbolo, JS la trasforma implicitamente in una stringa. Al contrario, Map accetta qualsiasi tipo di chiave: stringa, numero, valore booleano, simbolo ecc. E Map conserva il tipo di chiave originale. Qui useremo il numero come chiave all'interno di una mappa e rimarrà un numero:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

const keysOfMap= [...numbersMap.keys()];

console.log(keysOfMap);                        // [1, 2]

All'interno di una mappa possiamo persino usare un intero oggetto come chiave. Ci possono essere momenti in cui vogliamo archiviare alcuni dati relativi agli oggetti, senza collegare questi dati all'interno dell'oggetto stesso in modo da poter lavorare con oggetti snelli ma vogliamo archiviare alcune informazioni sull'oggetto. In questi casi dobbiamo usare Map in modo da poter rendere Object come chiave e i relativi dati dell'oggetto come valore.

const foo= {name: foo};

const bar= {name: bar};

const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];

Ma il rovescio della medaglia di questo approccio è la complessità dell'accesso al valore in base alla chiave, poiché è necessario scorrere l'intero array per ottenere il valore desiderato.

function getBy Key(kindOfMap, key) {
    for (const [k, v]  of kindOfMap) {
        if(key === k) {
            return v;
        }
    }
    return undefined;
}

getByKey(kindOfMap, foo);            // 'Foo related data'

Possiamo risolvere questo problema di non ottenere l'accesso diretto al valore utilizzando una mappa appropriata.

const foo= {name: 'foo'};

const bar= {name: 'bar'};

const myMap= new Map();

myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');

console.log(myMap.get(foo));            // 'Foo related data'

Avremmo potuto farlo usando WeakMap, basta scrivere, const myMap = new WeakMap (). Le differenze tra Map e WeakMap sono che WeakMap consente la garbage collection di chiavi (qui oggetti) in modo da prevenire perdite di memoria, WeakMap accetta solo oggetti come chiavi e WeakMap ha ridotto l'insieme di metodi.

2. La mappa non ha restrizioni sui nomi delle chiavi:

Per semplici oggetti JS possiamo sovrascrivere accidentalmente le proprietà ereditate dal prototipo e può essere pericoloso. Qui sovrascriveremo la proprietà toString () dell'oggetto attore:

const actor= {
    name: 'Harrison Ford',
    toString: 'Actor: Harrison Ford'
};

Ora definiamo un fn isPlainObject () per determinare se l'argomento fornito è un oggetto semplice e questo fn utilizza il metodo toString () per verificarlo:

function isPlainObject(value) {
    return value.toString() === '[object Object]';
}

isPlainObject(actor);        // TypeError : value.toString is not a function

// this is because inside actor object toString property is a string instead of inherited method from prototype

La mappa non ha restrizioni sui nomi delle chiavi, possiamo usare nomi di chiavi come toString, costruttore ecc. Qui, sebbene l'oggetto actMap abbia una proprietà chiamata toString ma il metodo toString () ereditato dal prototipo dell'oggetto actMap funziona perfettamente.

const actorMap= new Map();

actorMap.set('name', 'Harrison Ford');

actorMap.set('toString', 'Actor: Harrison Ford');

function isMap(value) {
  return value.toString() === '[object Map]';
}

console.log(isMap(actorMap));     // true

Se abbiamo una situazione in cui l'input dell'utente crea chiavi, allora dobbiamo prendere quelle chiavi all'interno di una mappa anziché un oggetto semplice. Questo perché l'utente può scegliere un nome di campo personalizzato come toString, costruttore ecc., Quindi tali nomi di chiave in un oggetto semplice possono potenzialmente rompere il codice che in seguito utilizza questo oggetto. Quindi la soluzione giusta è associare lo stato dell'interfaccia utente a una mappa, non c'è modo di interrompere la mappa:

const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);

3. La mappa è iterabile:

Per iterare le proprietà di un oggetto semplice abbiamo bisogno di Object.entries () o Object.keys (). Object.entries (plainObject) restituisce una matrice di coppie di valori-chiave estratte dall'oggetto, possiamo quindi distruggere quelle chiavi e valori e ottenere output di valori e chiavi normali.

const colorHex= {
  'white': '#FFFFFF',
  'black': '#000000'
}

for(const [color, hex] of Object.entries(colorHex)) {
  console.log(color, hex);
}
//
'white' '#FFFFFF'   
'black' '#000000'

Dato che le mappe sono iterabili, è per questo che non abbiamo bisogno di metodi entry () per iterare su una mappa e destrutturare una chiave, l'array di valori può essere fatto direttamente sulla mappa poiché all'interno di una mappa ogni elemento vive come un array di coppie di valori chiave separate da virgole .

const colorHexMap= new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');


for(const [color, hex] of colorHexMap) {
  console.log(color, hex);
}
//'white' '#FFFFFF'   'black' '#000000'

Inoltre map.keys () restituisce un iteratore su chiavi e map.values ​​() restituisce un iteratore su valori.

4. Possiamo facilmente conoscere le dimensioni di una mappa

Non possiamo determinare direttamente il numero di proprietà in un oggetto semplice. Abbiamo bisogno di un helper come Object.keys () che restituisce un array con le chiavi dell'oggetto, quindi usando la proprietà length possiamo ottenere il numero di chiavi o la dimensione dell'oggetto normale.

const exams= {'John Rambo': '80%', 'James Bond': '60%'};

const sizeOfObj= Object.keys(exams).length;

console.log(sizeOfObj);       // 2

Ma nel caso di Mappe possiamo avere accesso diretto alla dimensione della Mappa usando la proprietà map.size.

const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);

console.log(examsMap.size);

1

Questi due suggerimenti possono aiutarti a decidere se utilizzare una mappa o un oggetto:

  • Usa le mappe sugli oggetti quando le chiavi sono sconosciute fino al momento dell'esecuzione e quando tutte le chiavi sono dello stesso tipo e tutti i valori sono dello stesso tipo.

  • Utilizzare le mappe nel caso in cui sia necessario archiviare i valori primitivi come chiavi poiché l'oggetto considera ogni chiave come una stringa sia un valore numerico, un valore booleano o qualsiasi altro valore primitivo.

  • Utilizzare gli oggetti quando esiste una logica che opera su singoli elementi.

Fonte: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
Questi suggerimenti non sembrano particolarmente utili soprattutto perché tende a non essere facile dividere le cose secondo questi criteri. Non capisco con il primo perché le mappe sono un vantaggio quando chiavi / valori sono dello stesso tipo. Sembra più che stia cercando di dire usare oggetti come classi / strutture, mappe come raccolte. Il secondo è scritto male e non arriva al punto. Significa davvero usare le mappe quando si hanno tipi equivalenti di stringa mista ("1" e 1) o quando è necessario / si desidera conservare i tipi di chiave. L'ultima penso che sia la stessa della prima, presumo che tu non sappia cosa sia un oggetto, quindi è vago.
jgmjgm,

1

Questo è un modo breve per me di ricordarlo: KOI

  1. Keys. La chiave dell'oggetto è stringhe o simboli. Le chiavi della mappa possono anche essere numeri (1 e "1" sono diversi), oggetti NaN, ecc. Usa ===per distinguere tra le chiavi, con un'eccezione, NaN !== NaNma puoi usarle NaNcome chiave.
  2. Ordine. L'ordine di inserimento viene ricordato. Quindi [...map]o [...map.keys()]ha un ordine particolare.
  3. Interfaccia. Oggetto: obj[key]o obj.a(in qualche lingua, []e []=fanno davvero parte dell'interfaccia). Mappa ha get(), set(), has(), delete()ecc Si noti che è possibile utilizzare map[123], ma che sta usando come oggetto JS pianura.

0

Secondo Mozilla

Oggetto vs Mappa in JavaScript in breve con esempi.

L'oggetto segue lo stesso concetto di quello della mappa, vale a dire l'utilizzo della coppia chiave-valore per la memorizzazione dei dati. Ma ci sono lievi differenze che rendono la mappa un esecutore migliore in determinate situazioni.

Mappa- è una struttura di dati che aiuta a memorizzare i dati sotto forma di coppie. La coppia è composta da una chiave univoca e da un valore mappato alla chiave. Aiuta a prevenire la doppiezza.

Differenze chiave

  • La mappa è un'istanza di un oggetto ma il viceversa non è vero.

var map = new Map();
var obj = new Object(); 
console.log(obj instanceof Map);   // false
console.log(map instanceof Object);  // true

  • In Object, il tipo di dati del campo chiave è limitato a numeri interi, stringhe e simboli. Mentre in Map, il campo chiave può essere di qualsiasi tipo di dati (intero, un array, un oggetto)

var map = new Map();//Empty 
map.set(1,'1');
map.set('one', 1);
map.set('{}', {name:'Hello world'});
map.set(12.3, 12.3)
map.set([12],[12345])

for(let [key,value] of map.entries())
  console.log(key+'---'+value)

  • Nella mappa, l'ordine originale degli elementi è conservato. Questo non è vero nel caso di oggetti.

let obj ={
  1:'1',
  'one':1,
  '{}': {name:'Hello world'},
  12.3:12.3,
  [12]:[100]
}
console.log(obj)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.