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] = true
i 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ć hasOwnProperty
podczas 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.