Ключова відмінність полягає в тому, що Об'єкти підтримують лише рядкові клавіші там, де Карти підтримують більш-менш будь-який тип клавіш.
Якщо я зроблю obj[123] = true
і Object.keys(obj)
тоді я отримаю, ["123"]
а не [123]
. Карта зберегла б тип ключа та повернення, [123]
яке чудово. Карти також дозволяють використовувати Об'єкти як ключі. Традиційно для цього вам доведеться надати об’єктам якийсь унікальний ідентифікатор, щоб їх хеш (я не думаю, що я ніколи не бачив нічого подібногоgetObjectId
в JS як частина стандарту). Карти також гарантують збереження порядку, тому все краще для збереження, а іноді можуть заощадити, що вам потрібно зробити кілька видів.
Між картами та об'єктами на практиці існує кілька плюсів і мінусів. Об'єкти отримують як переваги, так і недоліки, будучи дуже щільно інтегрованими в ядро JavaScript, що відрізняє їх від суттєвої карти за межі різниці в ключовій підтримці.
Безпосередньою перевагою є те, що у вас є синтаксична підтримка об’єктів, що полегшує доступ до елементів. Ви також маєте пряму підтримку з JSON. Якщо використовується як хеш, дратується отримати об'єкт без властивостей. За замовчуванням, якщо ви хочете використовувати Об'єкти як хеш-таблицю, вони будуть забруднені, і вам часто доведеться звертатися hasOwnProperty
до них під час доступу до властивостей. Тут ви можете побачити, як за замовчуванням об'єкти забруднюються та як створювати сподіваються незабруднені об’єкти для використання як хеші:
({}).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
Забруднення об'єктів - це не тільки те, що робить код більш дратівливим, повільним тощо, але також може мати потенційні наслідки для безпеки.
Об'єкти не є чистими хеш-таблицями, але намагаються зробити більше. У вас виникають головні болі, такі як hasOwnProperty
, не можете легко отримати довжину ( Object.keys(obj).length
) тощо. Об'єкти призначені не для того, щоб використовуватись як хеш-карти, а як динамічні розширювані об'єкти, і тоді, коли ви використовуєте їх як чисті хеш-таблиці, виникають проблеми.
Порівняння / Перелік різних поширених операцій:
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();
Є кілька інших варіантів, підходів, методологій тощо з різними підйомами та падіннями (продуктивність, короткий, портативний, розширюваний тощо). Об'єкти трохи дивні, що є основними для мови, тому у вас є багато статичних методів роботи з ними.
Окрім переваги Карт у збереженні ключових типів, а також у змозі підтримувати такі речі, як об’єкти як ключі, вони відокремлені від побічних ефектів, які у об’єктів багато. Карта - це чистий хеш, немає ніякої плутанини в спробі бути об’єктом одночасно. Карти також можна легко розширити за допомогою функцій проксі. На даний момент у об’єкта є клас проксі, однак продуктивність та використання пам'яті похмурі, фактично створюючи власний проксі, схожий на те, що Map for Objects в даний час працює краще, ніж Proxy.
Істотним недоліком для Карт є те, що вони не підтримуються безпосередньо JSON. Парсинг можливий, але має кілька зависань:
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;
});
Вищезазначене введе серйозний хіт на продуктивність, а також не підтримує жодних рядкових клавіш. Кодування JSON ще складніше та проблематичніше (це один із багатьох підходів):
// 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())
});
};
Це не так вже й погано, якщо ви використовуєте Карти, але у вас будуть проблеми, коли ви змішуєте типи або використовуєте як скалярні значення як ключі (не те, що JSON ідеально підходить до такої проблеми, як це, наприклад, кругової посилання на об'єкт IE). Я не перевіряв його, але ймовірно, що це сильно пошкодить продуктивність порівняно зі строфікацією.
Інші мови сценаріїв часто не мають таких проблем, оскільки вони мають явні не скалярні типи для Map, Object та Array. Веб-розробка часто є болем з не скалярними типами, коли вам доводиться мати справу з такими речами, як PHP об'єднує Array / Map з Object, використовуючи A / M для властивостей, а JS об'єднує Map / Object з масивом, що розширює M / O. Об'єднання складних типів - це диявольська риса мов сценаріїв високого рівня.
Поки це в основному проблеми навколо впровадження, але важлива також і робота для основних операцій. Продуктивність також складна, оскільки це залежить від двигуна та використання. Пройдіть мої тести із зерном солі, оскільки я не можу виключити жодної помилки (мені доведеться це поспішити). Ви також повинні запустити свої власні тести, щоб підтвердити, як мій досліджує лише дуже конкретні прості сценарії, щоб дати лише грубі вказівки. Згідно з тестами в Chrome для дуже великих об'єктів / карт, продуктивність для об’єктів гірша через видалення, яке, очевидно, якось пропорційно кількості клавіш, а не 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 очевидно має сильну перевагу в отриманні та оновленнях, але ефективність видалення є надзвичайною. Карти використовують у цьому випадку на невеликий об'єм більше пам'яті (накладні витрати), але лише один об'єкт / карта тестується мільйонами клавіш, вплив накладних витрат на карти не виражається добре. З об'єктами управління пам'яттю також здається, що звільняються раніше, якщо я правильно читаю профіль, що може бути однією перевагою на користь об'єктів.
У Firefox для цього конкретного еталону йдеться про іншу історію:
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
Слід негайно зазначити, що в цьому конкретному еталоні видалення об'єктів у Firefox не викликає жодних проблем, проте в інших тестах це спричинило проблеми, особливо коли є багато клавіш, як і в Chrome. Карти явно переважають у Firefox для великих колекцій.
Однак це ще не кінець історії, а як щодо багатьох маленьких об'єктів чи карт? Я зробив швидкий орієнтир цього, але не вичерпного (налаштування / отримання) якого найкраще працює з невеликою кількістю клавіш у вищезазначених операціях. Цей тест детальніше стосується пам'яті та ініціалізації.
Map Create: 69 // new Map
Object Create: 34 // {}
Ці цифри знову різняться, але в основному Об'єкт має хороші позиції. У деяких випадках лідерство об'єктів над картами є надзвичайним (~ 10 разів краще), але в середньому це було приблизно в 2-3 рази краще. Здається, надзвичайні сплески продуктивності можуть працювати обома способами. Я протестував це лише в Chrome і створеннях для профільного використання пам'яті та накладних витрат. Я був дуже здивований, побачивши, що в Chrome видно, що Карти з одним ключем використовують приблизно в 30 разів більше пам’яті, ніж об’єкти з одним ключем.
Для тестування багатьох дрібних об'єктів з усіма перерахованими вище операціями (4 клавіші):
Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139
Що стосується розподілу пам’яті, вони поводилися однаково з точки зору звільнення / GC, але Map використовував у 5 разів більше пам’яті. Цей тест використовував 4 клавіші, де, як і в останньому тесті, я встановив лише одну клавішу, щоб це пояснило зменшення накладних витрат на пам'ять. Я кілька разів провів цей тест, і Map / Object - це більш-менш загальна шия та шия для Chrome з точки зору загальної швидкості. У Firefox для маленьких об’єктів є певна перевага в продуктивності перед загальними картами.
Звичайно, це не включає окремі варіанти, які можуть сильно відрізнятися. Я б не радив мікрооптимізацію з цими цифрами. З цього ви можете отримати те, що, як правило, розглядайте Карти більш сильно для дуже великих сховищ ключових значень та об'єктів для невеликих сховищ ключових цінностей.
Крім цього найкраща стратегія з цими двома - це реалізувати її та просто змусити її працювати спочатку. Під час профілювання важливо пам’ятати, що іноді речі, які ви не думаєте, будуть повільними, дивлячись на них, можуть бути надзвичайно повільними через химерність двигуна, як це видно із випадку видалення об’єктного ключа.