Mapa a obiekt w JavaScript


290

Właśnie odkryłem chromestatus.com i po kilku godzinach dnia znalazłem ten wpis :

Mapa: Obiekty mapy to proste mapy klucz / wartość.

Zdezorientowało mnie to. Zwykłe obiekty JavaScript to słowniki, więc czym Mapróżni się od słownika? Koncepcyjnie są identyczne (zgodnie z jaka jest różnica między mapą a słownikiem? )

Odwołania do dokumentacji chromestatus nie pomagają:

Obiekty mapy to zbiory par klucz / wartość, w których zarówno klucze, jak i wartości mogą być dowolnymi wartościami języka ECMAScript. Odrębna wartość klucza może wystąpić tylko w jednej parze klucz / wartość w kolekcji Mapy. Wyróżnij wartości kluczy jako dyskryminowane za pomocą algorytmu porównawczego, który jest wybierany podczas tworzenia mapy.

Obiekt Map może iterować swoje elementy w kolejności wstawiania. Obiekt mapy musi zostać zaimplementowany przy użyciu tabel skrótów lub innych mechanizmów, które średnio zapewniają czasy dostępu podliniowe względem liczby elementów w kolekcji. Struktury danych użyte w tej specyfikacji obiektów mapy służą jedynie do opisania wymaganej możliwej do zaobserwowania semantyki obiektów mapy. Nie jest to zamierzony model wykonawczy.

… Nadal brzmi dla mnie jak przedmiot, więc wyraźnie coś przeoczyłem.

Dlaczego JavaScript zyskuje (dobrze obsługiwany) Mapobiekt? Co to robi?


Odpowiedzi:


286

Według mozilli:

Obiekt Map może iterować swoje elementy w kolejności wstawiania - pętla for..of zwróci tablicę [klucz, wartość] dla każdej iteracji.

i

Obiekty są podobne do Map, ponieważ oba umożliwiają ustawianie kluczy na wartości, pobieranie tych wartości, usuwanie kluczy i wykrywanie, czy coś jest przechowywane pod kluczem. Z tego powodu Obiekty były historycznie używane jako Mapy; istnieją jednak istotne różnice między obiektami a mapami, które ułatwiają korzystanie z mapy.

Obiekt ma prototyp, więc na mapie są domyślne klucze. Można to jednak obejść, używając map = Object.create (null). Klucze obiektu to ciągi znaków, w których mogą mieć dowolną wartość dla mapy. Możesz łatwo uzyskać rozmiar mapy, podczas gdy musisz ręcznie śledzić rozmiar obiektu.

Używaj map nad obiektami, gdy klucze są nieznane do czasu uruchomienia, i gdy wszystkie klucze są tego samego typu, a wszystkie wartości są tego samego typu.

Używaj obiektów, gdy istnieje logika, która działa na poszczególne elementy.

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

Iterability-in-order to funkcja, która od dawna była pożądana przez programistów, częściowo dlatego, że zapewnia tę samą wydajność we wszystkich przeglądarkach. Więc dla mnie to duża sprawa.

myMap.has(key)Metoda będzie szczególnie przydatny, a także myMap.sizenieruchomości.


13
Minusem jest prawdopodobnie to, że mapa wymaga więcej pamięci (jednak w tym samym rzędzie wielkości), aby utrzymać porządek wstawiania.
John Kurlak

4
Mapy mają inne funkcje poza wspomnianą tutaj uporządkowaniem (używanie dowolnego obiektu jako klucza, separacja kluczy i rekwizytów itp.), Ale FWIW w niektórych przypadkach kolejność iteracji właściwości zwykłego obiektu jest zdefiniowana w ES2015. Zobacz stackoverflow.com/a/32149345 .
JMM

2
Nie zrozumiałem, kiedy mówisz: Obiekt ma prototyp, więc na mapie są domyślne klucze. Można to jednak obejść za pomocąmap = Object.create(null) . Jakie są klucze domyślne? Jak powiązane są klucze Object.prototype?
nadmierna wymiana

4
Moje testy w Chrome wykazały, że mapy nie używają żadnej znaczącej ilości pamięci do utrzymania porządku. Wydaje mi się, że za milion kluczy było o 0,1 KB więcej i nie sądzę, żeby miało to na celu utrzymanie porządku. Jednak to ~ 0,1 KB wydaje się być stałym narzutem. Jeśli zamiast tego utworzysz milion map z jednym kluczem i porównasz, jest znacznie większy niż obiekt.
jgmjgm

2
@luxon tworzysz tam obiekt. Specyfikacja ES6 wymaga new, aby operator był używany z Mapsymbolem, tzn new Map. Aby utworzyć obiekt mapy. var a = {}jest skrótem od (co oznacza odpowiednik)var a = Object.create(Object.prototype)
dudewad

104

Kluczową różnicą jest to, że Obiekty obsługują tylko klucze łańcuchowe, podczas gdy Mapy obsługują mniej więcej dowolny typ klucza.

Jeśli to zrobię obj[123] = truei wtedy Object.keys(obj)dostanę ["123"]zamiast [123]. Mapa zachowałaby typ klucza i powrót, [123]co jest świetne. Mapy pozwalają również używać obiektów jako kluczy. Tradycyjnie, aby to zrobić, musiałbyś nadać obiektom pewien unikalny identyfikator, aby je haszować (nie sądzę, żebym kiedykolwiek widział coś takiegogetObjectId w JS jako część standardu). Mapy gwarantują również zachowanie porządku, więc są lepsze do zachowania i czasami mogą zaoszczędzić Ci konieczności zrobienia kilku rodzajów.

Pomiędzy mapami i obiektami w praktyce istnieje kilka zalet i wad. Obiekty zyskują zarówno zalety, jak i wady, ponieważ są bardzo ściśle zintegrowane z rdzeniem JavaScript, co odróżnia je znacznie od mapy poza różnicą w kluczowej obsłudze.

Bezpośrednią zaletą jest to, że masz obsługę syntaktyczną obiektów, co ułatwia dostęp do elementów. Masz również bezpośrednie wsparcie dla JSON. W przypadku użycia skrótu denerwujące jest uzyskanie obiektu bez żadnych właściwości. Domyślnie, jeśli chcesz używać obiektów jako tabeli skrótów, zostaną one zanieczyszczone i często będziesz musiał je wywoływać hasOwnPropertypodczas uzyskiwania dostępu do właściwości. Możesz tutaj zobaczyć, jak domyślnie obiekty są zanieczyszczane i jak, mam nadzieję, tworzyć nieskażone obiekty do użycia jako skróty:

({}).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

Zanieczyszczenia obiektów to nie tylko coś, co czyni kod bardziej irytującym, wolniejszym itp., Ale może również mieć potencjalne konsekwencje dla bezpieczeństwa.

Obiekty nie są czystymi tablicami skrótu, ale starają się zrobić więcej. Masz bóle głowy hasOwnProperty, ponieważ nie jesteś w stanie łatwo uzyskać długości ( Object.keys(obj).length) i tak dalej. Obiekty nie mają być używane wyłącznie jako mapy skrótów, ale również jako dynamiczne rozszerzalne obiekty, więc gdy używasz ich jako tabel skrótów, pojawiają się problemy.

Porównanie / lista różnych popularnych operacji:

    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();

Istnieje kilka innych opcji, podejść, metodologii itp. Z różnymi wzlotami i upadkami (wydajność, zwięzłe, przenośne, rozszerzalne itp.). Przedmioty są nieco dziwne, ponieważ są rdzeniem języka, więc masz wiele statycznych metod pracy z nimi.

Oprócz zalet map, które zachowują typy kluczy, a także są w stanie obsługiwać takie obiekty, jak klucze, są one odizolowane od skutków ubocznych, które wiele obiektów ma. Mapa to czysty skrót, nie ma wątpliwości co do próby bycia obiektem w tym samym czasie. Mapy można również łatwo rozszerzać za pomocą funkcji proxy. Obiekty mają obecnie klasę proxy, jednak wydajność i użycie pamięci są ponure, w rzeczywistości tworzenie własnego proxy, który wygląda jak mapa dla obiektów, działa obecnie lepiej niż proxy.

Istotną wadą Map jest to, że nie są one obsługiwane bezpośrednio w JSON. Analiza jest możliwa, ale ma kilka zawieszeń:

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;
});

Powyższe wprowadzi poważny hit wydajności, a także nie będzie obsługiwać żadnych kluczy ciągów. Kodowanie JSON jest jeszcze trudniejsze i bardziej problematyczne (jest to jedno z wielu podejść):

// 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())
    });
};

Nie jest to takie złe, jeśli używasz wyłącznie Map, ale będziesz mieć problemy, gdy miksujesz typy lub używasz wartości nieskalarnych jako kluczy (nie to, że JSON jest idealny z takim rodzajem problemu, jak odwołanie do okrągłego obiektu IE). Nie testowałem tego, ale są szanse, że poważnie zaszkodzi to wydajności w porównaniu do stringify.

Inne języki skryptowe często nie mają takich problemów, ponieważ mają jawne typy nieskalarne dla Map, Object i Array. Tworzenie stron internetowych jest często uciążliwe dla typów nieskalarnych, w których musisz radzić sobie z takimi rzeczami, jak PHP łączy Array / Map z Object przy użyciu A / M dla właściwości, a JS łączy Map / Object z Array rozszerzającym M / O. Łączenie typów złożonych jest diabelską zmorą języków skryptowych wysokiego poziomu.

Jak dotąd są to głównie problemy związane z implementacją, ale ważna jest również wydajność podstawowych operacji. Wydajność jest również złożona, ponieważ zależy od silnika i użytkowania. Zrób testy z odrobiną soli, ponieważ nie mogę wykluczyć żadnego błędu (muszę to przyspieszyć). Powinieneś również uruchomić własne testy, aby potwierdzić, ponieważ moje sprawdzają tylko bardzo konkretne proste scenariusze, aby dać jedynie przybliżone wskazanie. Według testów w Chrome dla bardzo dużych obiektów / map wydajność obiektów jest gorsza z powodu usuwania, które najwyraźniej jest w pewnym stopniu proporcjonalne do liczby kluczy, a nie 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 ma wyraźną przewagę w pobieraniu i aktualizowaniu, ale wydajność usuwania jest przerażająca. Mapy zużywają w tym przypadku odrobinę więcej pamięci (narzut), ale przy testowaniu tylko jednego obiektu / mapy z milionami kluczy wpływ narzutu na mapy nie jest dobrze wyrażony. W przypadku zarządzania pamięcią obiekty również wydają się zwalniać wcześniej, jeśli poprawnie czytam profil, co może być jedną zaletą na rzecz obiektów.

W Firefoksie dla tego konkretnego testu porównawczego jest to inna historia:

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

Powinienem od razu zaznaczyć, że w tym konkretnym teście testowym usunięcie obiektów w Firefoksie nie powoduje żadnych problemów, jednak w innych testach spowodowało problemy, zwłaszcza gdy jest wiele kluczy, tak jak w Chrome. Mapy są wyraźnie lepsze w Firefoksie w przypadku dużych kolekcji.

To jednak nie koniec historii, a co z wieloma małymi obiektami lub mapami? Zrobiłem szybki test tego, ale nie wyczerpujący (ustawienie / uzyskanie), który działa najlepiej z niewielką liczbą klawiszy w powyższych operacjach. Ten test dotyczy bardziej pamięci i inicjalizacji.

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

Ponownie liczby te są różne, ale w zasadzie Object ma dobrą przewagę. W niektórych przypadkach przewaga obiektów nad mapami jest ekstremalna (~ 10 razy lepsza), ale średnio około 2-3 razy lepsza. Wydaje się, że ekstremalne skoki wydajności mogą działać w obie strony. Testowałem to tylko w Chrome i tworzeniu, aby profilować użycie pamięci i koszty ogólne. Byłem bardzo zaskoczony, widząc, że w Chrome wygląda na to, że Mapy z jednym klawiszem zużywają około 30 razy więcej pamięci niż Obiekty z jednym kluczem.

Do testowania wielu małych obiektów wszystkimi powyższymi operacjami (4 klawisze):

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

Pod względem alokacji pamięci zachowywały się one tak samo pod względem uwalniania / GC, ale Map używał 5 razy więcej pamięci. W tym teście wykorzystano 4 klawisze, w których, podobnie jak w ostatnim teście, ustawiłem tylko jeden klawisz, co wyjaśniałoby zmniejszenie narzutu pamięci. Przeprowadziłem ten test kilka razy, a mapa / obiekt są mniej więcej ogólnie dostępne dla Chrome pod względem ogólnej prędkości. W Firefoksie dla małych obiektów istnieje wyraźna przewaga wydajności nad mapami.

To oczywiście nie obejmuje poszczególnych opcji, które mogą się znacznie różnić. Z tymi liczbami nie radziłbym mikrooptymalizacji. To, co możesz z tego wyciągnąć, polega na tym, że silniej rozważ Mapy w przypadku bardzo dużych magazynów wartości kluczowych i obiektów dla magazynów o małych wartościach kluczowych.

Poza tym najlepsza strategia z tymi dwoma, aby ją wdrożyć i po prostu sprawić, by działała jako pierwsza. Podczas profilowania ważne jest, aby pamiętać, że czasami rzeczy, o których nie pomyślałbyś, że byłyby powolne, patrząc na nie, mogą być niewiarygodnie wolne z powodu dziwactw silnikowych, jak widać w przypadku usuwania klucza obiektu.


Brak możliwości serializacji był prawdziwym problemem dla wielu programistów. Spójrz na opinię jak utrzymać mapę ES6 w localstorage (lub gdzie indziej)? i w jaki sposób JSON.stringify mapę ES6? .
Franklin Yu,

Czy liczba jest wyrażona w milisekundach, bajtach lub sumie obiektów?
StefansArya

Zajęło to ms (coś zabierało krótko mówiąc o czymś używanym, więc w tym przypadku zużywa czas). Chociaż jest to stary test i nie mam już kodu testu porównawczego. Prawdopodobnie teraz jest zupełnie inaczej. Na przykład problem usuwania został rozwiązany.
jgmjgm

27

Nie sądzę, aby w odpowiedzi do tej pory wspomniano następujące punkty i pomyślałem, że warto o nich wspomnieć.


Mapy mogą być większe

W chrome mogę uzyskać 16,7 miliona par klucz / wartość w Mapporównaniu do 11,1 miliona w przypadku zwykłego obiektu. Prawie dokładnie 50% więcej par z a Map. Oba zajmują około 2 GB pamięci przed awarią, więc myślę, że może to być związane z ograniczaniem pamięci przez chrom ( Edytuj : Tak, spróbuj wypełnić 2, Mapsa dostaniesz tylko 8,3 miliona par przed awarią). Możesz to przetestować samodzielnie za pomocą tego kodu (uruchom je osobno i oczywiście nie w tym samym czasie):

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") }
}

Obiekty mają już pewne właściwości / klucze

Ten wcześniej mnie potknął. Regularne obiekty mają toString, constructor, valueOf, hasOwnProperty,isPrototypeOf i kilka innych wcześniej istniejących właściwości. To może nie być duży problem w większości przypadków użycia, ale wcześniej sprawiał mi problemy.

Mapy mogą być wolniejsze:

Ze względu na .getnarzut wywołania funkcji i brak wewnętrznej optymalizacji, Map może być znacznie wolniejszy niż zwykły stary obiekt JavaScript dla niektórych zadań.


1
Czy Twoim zdaniem semantyka przeważa tutaj nad wydajnością? Mapy brzmią idealnie, jeśli potrzebujesz słownika, ale trudno jest zaakceptować wolniejsze wyszukiwanie. Czy szybkie wyszukiwanie całego słownika nie jest szybkie?
user2954463,

3
Na pewno tam ze zwykłego starych obiektów, jeśli jesteś w porządku z 11 milionów par klucz / wartość i nie dbają o istniejące wcześniej klawiszy takich jak toString, constructoritd (czyli klucze są bardzo mało prawdopodobne, aby zderzyć się z nich). Łatwiej jest z nimi pracować - np. Przyrost jest obj[i] = (obj[i] || 0) + 1, podczas gdy z Maptym, map.set(i, (map.get(i) || 0) + 1)co wciąż nie jest takie złe, ale pokazuje tylko, jak rzeczy mogą się niepotrzebnie bałaganić. Mapy zdecydowanie mają swoje przypadki użycia, ale często wystarczy zwykły obiekt.

1
Należy pamiętać, że można pozbyć się domyślnie toString, constructor(itd.) Właściwości obiektu poprzez pisanie obj = Object.create(null)zamiast obj = {}.

18

Obiekty mogą zachowywać się jak słowniki, ponieważ JavaScript jest dynamicznie wpisywany, co pozwala dodawać lub usuwać właściwości obiektu w dowolnym momencie.

Ale nowa Map()funkcjonalność jest znacznie lepsza, ponieważ:

  • Zapewnia get, set, hasi deletemetod.
  • Akceptuje dowolny typ kluczy zamiast tylko ciągów znaków.
  • Zapewnia iterator dla łatwego for-ofużycia i utrzymuje porządek wyników.
  • Nie ma przypadków krawędzi z prototypami i innymi właściwościami wyświetlanymi podczas iteracji lub kopiowania.
  • Obsługuje miliony przedmiotów.
  • Jest bardzo szybki i staje się coraz szybszy, ponieważ silniki javascript stają się coraz lepsze.

99% czasu należy po prostu użyć Map() . Jeśli jednak używasz tylko kluczy opartych na ciągach znaków i potrzebujesz maksymalnej wydajności odczytu, obiekty mogą być lepszym wyborem.

Chodzi o to, że (prawie wszystkie) silniki javascript kompilują obiekty do klas C ++ w tle. Te typy są buforowane i ponownie używane według „konturu”, więc gdy utworzysz nowy obiekt o tych samych dokładnych właściwościach, silnik ponownie użyje istniejącej klasy tła. Ścieżka dostępu do właściwości w tych klasach jest bardzo zoptymalizowana i znacznie szybsza niż wyszukiwanieMap() .

Dodanie lub usunięcie właściwości powoduje ponowną kompilację buforowanej klasy zaplecza, dlatego używanie obiektu jako słownika z dużą ilością dodawanych i usuwanych kluczy jest bardzo wolne, ale odczytywanie i przypisywanie istniejących kluczy bez zmiany obiektu są bardzo szybkie.

Jeśli więc masz duże obciążenie do odczytu przy użyciu kluczy łańcuchowych, użyj objectspecjalistycznego słownika o wysokiej wydajności, ale do wszystkiego innego używaj Map().


Obiekt zapewnia get set has deleterównież funkcjonalność itp. Po prostu nie jest tak elegancki (ale też niezły). W jaki sposóbMap łatwiej jest używać iteracji? Nie jestem pewien, czy mogę się zgodzić.
Andrew

@Andrew Rozważam metody, a ich funkcjonalność jest również inna w zależności od tego, czego używasz i wyniku. Iteracja jest łatwiejsza, ponieważ właściwości prototypowe i rodzime nie są wyświetlane w pętli i używa normalnego iteratora JS, który utrzymuje tę samą kolejność.
Mani Gandham

11

Oprócz innych odpowiedzi odkryłem, że Mapy są bardziej nieporęczne i pełne w obsłudze niż obiekty.

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

Jest to ważne, ponieważ krótszy kod jest szybszy do odczytania, bardziej bezpośrednio ekspresyjny i lepiej trzymany w głowie programisty .

Kolejny aspekt: ​​ponieważ set () zwraca mapę, a nie wartość, nie można łączyć przypisań.

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

Debugowanie map jest również bardziej bolesne. Poniżej nie możesz zobaczyć, jakie klucze są na mapie. Aby to zrobić, musisz napisać kod.

Powodzenia w ocenie Iteratora map

Obiekty mogą być oceniane przez dowolne IDE:

WebStorm oceniający obiekt


4
Biorąc to wszystko pod uwagę, wydaje się, że mapa jest przedwczesną optymalizacją.
PRMan

10

Podsumowanie:

  • Object: Struktura danych, w której dane są przechowywane jako pary kluczowych wartości. W obiekcie klucz musi być liczbą, łańcuchem lub symbolem. Wartością może być dowolna wartość, więc również inne obiekty, funkcje itp. Obiekt jest niezorganizowaną strukturą danych, tzn. Sekwencja wstawiania par klucz-wartość nie jest zapamiętywana
  • ES6 Map: Struktura danych, w której dane są przechowywane jako pary kluczowych wartości. W którym unikalny klucz odwzorowuje wartość . Zarówno klucz, jak i wartość mogą mieć dowolny typ danych . Mapa jest iterowalną strukturą danych, co oznacza, że ​​sekwencja wstawiania jest zapamiętywana i że możemy uzyskać dostęp do elementów np. W for..ofpętli

Kluczowe różnice:

  • A Mapjest uporządkowane i iterowalne, podczas gdy obiekty nie są uporządkowane i iterowalne

  • Możemy umieścić dowolny typ danych jako Mapklucz, podczas gdy obiekty mogą mieć tylko liczbę, ciąg znaków lub symbol jako klucz.

  • A Mapdziedziczy Map.prototype. Zapewnia to wszelkiego rodzaju funkcje i właściwości użytkowe, co Mapznacznie ułatwia pracę z obiektami.

Przykład:

obiekt:

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)

Mapa:

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 () {}

źródło: MDN


4

Oprócz bycia iterowalnym w ściśle określonej kolejności i możliwości użycia dowolnych wartości jako kluczy (oprócz -0), mapy mogą być przydatne z następujących powodów:

  • Specyfikacja wymusza średnio operacje sublinearne.

    Każda nie głupia implementacja obiektu będzie używać tabeli skrótów lub podobnej, więc wyszukiwania właściwości będą prawdopodobnie średnio stałe. Wtedy obiekty mogą być nawet szybsze niż mapy. Ale nie jest to wymagane przez specyfikację.

  • Obiekty mogą mieć nieprzyjemne nieoczekiwane zachowania.

    Załóżmy na przykład, że nie ustawiłeś żadnej foowłaściwości dla nowo utworzonego obiektu obj, więc spodziewaj obj.foosię, że zwrócisz niezdefiniowany. Ale foomoże być wbudowaną własnością odziedziczoną Object.prototype. Lub próbujesz utworzyć obj.fooprzy użyciu przydziału, ale jakiś setter Object.prototypeuruchamia się zamiast zapisywać twoją wartość.

    Mapy zapobiegają tego typu rzeczom. Cóż, chyba że jakiś skrypt popełni błąd Map.prototype. I też Object.create(null)by działało, ale wtedy tracisz prostą składnię inicjującą obiekt.


4

Kiedy używać Map zamiast zwykłych Obiektów JavaScript?

Zwykły obiekt JavaScript {klucz: „wartość”} przechowuje uporządkowane dane. Ale zwykły obiekt JS ma swoje ograniczenia:

  1. Tylko łańcuchy i symbole mogą być używane jako klucze Obiektów. Jeśli użyjemy czegoś innego, powiedzmy, liczby jako klucze obiektu, to podczas uzyskiwania dostępu do tych kluczy zobaczymy, że klucze te zostaną przekonwertowane na ciągi pośrednio powodujące utratę spójności typów. const names = {1: „one”, 2: „two”}; Object.keys (names); // [„1”, „2”]

  2. Istnieje prawdopodobieństwo przypadkowego zastąpienia odziedziczonych właściwości po prototypach poprzez zapisanie identyfikatorów JS jako kluczowych nazw obiektów (np. ToString, konstruktor itp.)

  3. Innego obiektu nie można użyć jako klucza obiektu, więc nie można zapisać żadnych dodatkowych informacji dla obiektu, pisząc ten obiekt jako klucz innego obiektu, a wartość tego innego obiektu będzie zawierać dodatkowe informacje

  4. Obiekty nie są iteratorami

  5. Rozmiar obiektu nie może być określony bezpośrednio

Te ograniczenia obiektów są rozwiązywane przez Maps, ale musimy traktować je jako uzupełnienie obiektów zamiast ich zastępowania. Zasadniczo Map to po prostu tablica tablic, ale musimy przekazać tę tablicę tablic do obiektu Map jako argument z nowym słowem kluczowym, w przeciwnym razie tylko dla tablic tablic przydatne właściwości i metody Map nie są dostępne. I pamiętajcie o parach klucz-wartość wewnątrz tablicy tablic, w przeciwnym razie Mapa musi być oddzielona przecinkami, bez dwukropków jak w zwykłych obiektach.

3 wskazówki, aby zdecydować, czy użyć mapy, czy obiektu:

  1. Używaj map nad obiektami, gdy klucze są nieznane do czasu uruchomienia, ponieważ klucze utworzone przez dane wprowadzone przez użytkownika lub nieświadomie mogą uszkodzić kod, który używa obiektu, jeśli klucze te zastąpią odziedziczone właściwości obiektu, więc mapa jest w tych przypadkach bezpieczniejsza. Używaj także map, gdy wszystkie klucze są tego samego typu, a wszystkie mapy są tego samego typu.

  2. Użyj map, jeśli zachodzi potrzeba przechowywania prymitywnych wartości jako kluczy.

  3. Użyj obiektów, jeśli musimy operować na poszczególnych elementach.

Korzyści z korzystania z Map to:

1. Mapa akceptuje dowolny typ klucza i zachowuje typ klucza:

Wiemy, że jeśli klucz obiektu nie jest łańcuchem ani symbolem, to JS domyślnie przekształca go w łańcuch. Przeciwnie, Map akceptuje dowolny typ kluczy: ciąg znaków, liczbę, wartość logiczną, symbol itp., A Map zachowuje oryginalny typ klucza. Tutaj użyjemy liczby jako klucza wewnątrz mapy i pozostanie liczbą:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

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

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

Wewnątrz mapy możemy nawet użyć całego obiektu jako klucza. Może się zdarzyć, że będziemy chcieli przechowywać niektóre dane związane z obiektem, bez dołączania tych danych do samego obiektu, abyśmy mogli pracować z obiektami lean, ale chcemy przechowywać pewne informacje o obiekcie. W takich przypadkach musimy użyć mapy, aby obiekt mógł być kluczem, a powiązane dane obiektu - wartością.

const foo= {name: foo};

const bar= {name: bar};

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

Ale wadą tego podejścia jest złożoność dostępu do wartości według klucza, ponieważ musimy pętlować całą tablicę, aby uzyskać pożądaną wartość.

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

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

Możemy rozwiązać ten problem braku bezpośredniego dostępu do wartości za pomocą odpowiedniej mapy.

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'

Mogliśmy to zrobić za pomocą WeakMap, wystarczy napisać, const myMap = new WeakMap (). Różnice między Mapą i WeakMap polegają na tym, że WeakMap pozwala na odśmiecanie kluczy (tutaj obiektów), więc zapobiega wyciekom pamięci, WeakMap akceptuje tylko obiekty jako klucze, a WeakMap ma ograniczony zestaw metod.

2. Mapa nie ma ograniczeń co do nazw kluczy:

W przypadku zwykłych obiektów JS możemy przypadkowo zastąpić własność odziedziczoną po prototypie i może to być niebezpieczne. Tutaj zastąpimy właściwość toString () obiektu aktora:

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

Teraz zdefiniujmy fn isPlainObject (), aby ustalić, czy podany argument jest zwykłym obiektem, a ten fn używa metody toString (), aby to sprawdzić:

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

Mapa nie ma żadnych ograniczeń dotyczących nazw kluczy, możemy używać nazw kluczy takich jak toString, konstruktor itp. Tutaj chociaż obiekt actorMap ma właściwość o nazwie toString, ale metoda toString () odziedziczona z prototypu obiektu actorMap działa doskonale.

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

Jeśli mamy sytuację, w której dane wejściowe użytkownika tworzą klucze, musimy wziąć te klucze do mapy zamiast do zwykłego obiektu. Wynika to z faktu, że użytkownik może wybrać niestandardową nazwę pola, np. ToString, konstruktor itp., A następnie takie nazwy kluczy w prostym obiekcie mogą potencjalnie uszkodzić kod, który później używa tego obiektu. Tak więc właściwym rozwiązaniem jest powiązanie stanu interfejsu użytkownika z mapą, nie ma sposobu na złamanie mapy:

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

3. Mapa jest iterowalna:

Do iteracji właściwości zwykłego obiektu potrzebujemy Object.entries () lub Object.keys (). Object.entries (plainObject) zwraca tablicę par wartości klucza wyodrębnionych z obiektu, możemy następnie zniszczyć te klucze i wartości oraz uzyskać normalne klucze i wartości wyjściowe.

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

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

Ponieważ mapy są iterowalne, dlatego nie potrzebujemy metod entry () do iteracji po mapie i niszczenia klucza, tablicę wartości można wykonać bezpośrednio na mapie, ponieważ wewnątrz mapy każdy element żyje jako tablica par klucz-wartość oddzielonych przecinkami .

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'

Również map.keys () zwraca iterator po kluczach, a map.values ​​() zwraca iterator po wartościach.

4. Możemy łatwo poznać rozmiar mapy

Nie możemy bezpośrednio określić liczby właściwości w zwykłym obiekcie. Potrzebujemy pomocnika fn, Object.keys (), który zwraca tablicę z kluczami obiektu, a następnie za pomocą właściwości length możemy uzyskać liczbę kluczy lub rozmiar zwykłego obiektu.

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

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

console.log(sizeOfObj);       // 2

Ale w przypadku map możemy mieć bezpośredni dostęp do wielkości mapy za pomocą właściwości map.size.

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

console.log(examsMap.size);

1

Te dwie wskazówki pomogą Ci zdecydować, czy użyć mapy, czy obiektu:

  • Używaj map nad obiektami, gdy klucze są nieznane do czasu uruchomienia, i gdy wszystkie klucze są tego samego typu, a wszystkie wartości są tego samego typu.

  • Użyj map w przypadku, gdy zachodzi potrzeba przechowywania pierwotnych wartości jako kluczy, ponieważ obiekt traktuje każdy klucz jako ciąg znaków albo jego wartość liczbową, wartość logiczną lub dowolną inną pierwotną wartość.

  • Używaj obiektów, gdy istnieje logika, która działa na poszczególne elementy.

Źródło: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
Te wskazówki nie wydają się szczególnie pomocne, zwłaszcza że podzielenie rzeczy według tych kryteriów nie jest łatwe. Nie rozumiem po raz pierwszy, dlaczego mapy są korzystne, gdy klucze / wartości są tego samego typu. To brzmi bardziej jak próba użycia obiektów takich jak klasy / struktury, mapy takie jak kolekcje. Drugi napisany jest źle, nie przechodząc do sedna. To naprawdę oznacza używanie map, gdy masz mieszane równoważne typy ciągów („1” i 1) lub gdy potrzebujesz / chcesz zachować kluczowe typy. Ostatnie myślę, że to samo co pierwsze, zakładając, że nie wiesz, czym jest obiekt, więc jest niejasny.
jgmjgm

1

Jest to krótki sposób, aby zapamiętać: KOI

  1. Klucze. Kluczem obiektu są ciągi znaków lub symbole. Kluczami mapy mogą być również cyfry (1 i „1” są różne), obiekty NaNitp. Wykorzystuje ===się je do rozróżnienia kluczy, z jednym wyjątkiem, NaN !== NaNale można użyć go NaNjako klucza.
  2. Zamówienie. Kolejność wstawiania zostaje zapamiętana. Więc [...map]lub [...map.keys()]ma konkretne zamówienie.
  3. Berło. Obiekt: obj[key]lub obj.a(w jakimś języku []i []=naprawdę są częścią interfejsu). Mapa ma get(), set(), has(), delete()itd. Należy pamiętać, że można użyć map[123], ale że używa go jako zwykły obiekt JS.

0

Według Mozilli

Obiekt a mapa w JavaScript w skrócie z przykładami.

Obiekt- stosuje tę samą koncepcję co mapa, tzn. Używa pary klucz-wartość do przechowywania danych. Istnieją jednak niewielkie różnice, które sprawiają, że mapa jest lepsza w niektórych sytuacjach.

Mapa- to struktura danych, która pomaga w przechowywaniu danych w postaci par. Para składa się z unikalnego klucza i wartości odwzorowanej na klucz. Pomaga zapobiegać podwójności.

Kluczowe różnice

  • Mapa jest instancją obiektu, ale odwrotnie nie jest prawdą.

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

  • W Object typ danych pola klucza jest ograniczony do liczb całkowitych, łańcuchów i symboli. Podczas gdy w Mapie pole klucza może być dowolnego typu danych (liczba całkowita, tablica, obiekt)

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)

  • Na mapie zachowana jest pierwotna kolejność elementów. Nie dotyczy to obiektów.

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


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.