Карта проти об’єкта в JavaScript


290

Я щойно відкрив Chromeestatus.com і, втративши кілька годин свого дня, знайшов цю функцію :

Карта: Об'єкти карт - це прості карти ключа / значення.

Це мене збентежило. Регулярні об’єкти JavaScript є словниками, то чим він Mapвідрізняється від словника? Концептуально вони однакові (згідно з чим різниця між картою та словником? )

Посилання на документацію щодо хроместату також не допомагають:

Об'єкти карт - це набори пар ключів / значень, де і ключі, і значення можуть бути довільними значеннями мови ECMAScript. Виразне значення ключа може виникати лише в одній парі ключ / значення в колекції Map. Визначте ключові значення як розрізнені за допомогою алгоритму порівняння, який вибирається під час створення карти.

Об'єкт Map може повторювати його елементи в порядку вставки. Об'єкт Map повинен бути реалізований за допомогою хеш-таблиць або інших механізмів, які в середньому забезпечують час доступу, що є підлінійним за кількістю елементів колекції. Структури даних, використовувані в цій специфікації об'єктів Map, призначені лише для опису необхідної спостережуваної семантики об'єктів Map. Він не повинен бути життєздатною моделлю реалізації.

… Все ще звучить як предмет для мене, так явно я щось пропустив.

Чому JavaScript отримує (добре підтримуваний) Mapоб’єкт? Що це робить?


Відповіді:


286

Згідно з мозілою:

Об'єкт Map може повторити його елементи в порядку вставки - цикл for..of поверне масив [ключ, значення] для кожної ітерації.

і

Об'єкти схожі на "Карти", оскільки вони дозволяють встановлювати значення для клавіш, витягувати ці значення, видаляти ключі та визначати, чи зберігається щось у ключі. Через це Об'єкти історично використовувались як Карти; однак існують важливі відмінності між Об’єктами та Картами, які покращують використання Карти.

Об'єкт має прототип, тому на карті є ключі за замовчуванням. Однак це можна обійти за допомогою map = Object.create (null). Ключі об’єкта - це рядки, де вони можуть бути будь-яким значенням для карти. Ви можете легко отримати розмір карти, в той час як вам доведеться вручну відстежувати розмір об’єкта.

Використовуйте карти над об'єктами, коли ключі до часу запуску невідомі, і коли всі ключі одного типу і всі значення одного типу.

Використовуйте об'єкти, коли є логіка, яка діє на окремі елементи.

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

Порядок ітерабельності на замовлення - це функція, яку давно хочуть розробники, частково тому, що вона забезпечує однакову продуктивність у всіх браузерах. Тож для мене це великий.

myMap.has(key)Метод буде особливо зручно, а також myMap.sizeнерухомість.


13
Мінусом, мабуть, є те, що для підтримки порядку вставки для Карти потрібно більше пам’яті (в тому ж порядку величини).
Джон Курлак

4
Карти мають інші функції, окрім впорядкованості, які були згадані тут (використання будь-якого об’єкта як ключа, розділення ключів та реквізитів тощо), але FWIW в деяких випадках порядок ітерації властивостей простого об'єкта визначається ES2015. Див. Stackoverflow.com/a/32149345 .
JMM

2
Я не отримав сенсу, коли ти кажеш, що в «Об’єкт» є прототип, тому на карті є ключі за замовчуванням. Однак це можна обійти шляхом використанняmap = Object.create(null) . Що таке клавіші за замовчуванням? Як ключі пов’язані Object.prototype?
переоцінка

4
Мої тести в Chrome показали, що карти не використовують значну кількість більше пам'яті для підтримки порядку. Я маю на увазі, що на мільйон ключів було на 0,1 КБ більше, і я не думаю, що це було для підтримки порядку. Однак це ~ 0,1 КБ, здається, є постійною накладною витратою. Якщо ви створили мільйон карт за допомогою одного ключа замість цього і порівняєте, він набагато більший за об'єкт.
jgmjgm

2
@luxon ви створюєте об’єкт там. Специфікація ES6 вимагає використання newоператора із Mapсимволом, тобто new Mapдля створення об’єкта карти. var a = {}є скороченням (означає еквівалентно)var a = Object.create(Object.prototype)
dudewad

104

Ключова відмінність полягає в тому, що Об'єкти підтримують лише рядкові клавіші там, де Карти підтримують більш-менш будь-який тип клавіш.

Якщо я зроблю 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 для маленьких об’єктів є певна перевага в продуктивності перед загальними картами.

Звичайно, це не включає окремі варіанти, які можуть сильно відрізнятися. Я б не радив мікрооптимізацію з цими цифрами. З цього ви можете отримати те, що, як правило, розглядайте Карти більш сильно для дуже великих сховищ ключових значень та об'єктів для невеликих сховищ ключових цінностей.

Крім цього найкраща стратегія з цими двома - це реалізувати її та просто змусити її працювати спочатку. Під час профілювання важливо пам’ятати, що іноді речі, які ви не думаєте, будуть повільними, дивлячись на них, можуть бути надзвичайно повільними через химерність двигуна, як це видно із випадку видалення об’єктного ключа.


Відсутність серіалізаційності було справжнім болем для багатьох розробників. Подивіться на висновок: Як зберегти карту ES6 в локальному сховищі (або в іншому місці)? і як ви JSON.stringify ES6 Map? .
Франклін Ю

Чи число в мілісекундах, байтах чи загальних об'єктах?
StefansArya

Взяв так мс (щось потрібно коротке, щоб сказати щось використано, тому він використовує час у цьому випадку) Хоча це старий тест, і у мене більше немає базового коду. Напевно, зараз дуже різне. Проблема з видаленням, наприклад, я вважаю, виправлена.
jgmjgm

27

Я не думаю, що такі відповіді досі згадувалися у відповідях, і я вважав, що їх варто було б згадати.


Карти можуть бути більшими

У хромі я можу отримати 16,7 мільйонів пар ключів / значень Mapпроти 11,1 мільйона за допомогою звичайного об'єкта. Майже рівно на 50% більше пар з a Map. Вони обидва займають близько 2 Гб оперативної пам’яті перед тим, як вийти з ладу, і тому я думаю, що це може стосуватися обмеження пам’яті хромом ( Редагувати : Так, спробуйте заповнити 2, Mapsі ви отримаєте лише 8,3 мільйона пар перед тим, як він вийде з ладу). Ви можете перевірити його за допомогою цього коду (очевидно, запускайте їх окремо, але не водночас):

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

Об'єкти вже мають деякі властивості / ключі

Цей раніше мене відштовхував. Звичайні об'єкти мають toString, constructor, valueOf, hasOwnProperty, isPrototypeOfі купа інших вже існуючих властивостей. Це може не бути великою проблемою для більшості випадків використання, але це викликало проблеми у мене раніше.

Карти можуть бути повільнішими:

Завдяки .getнакладним викликам функцій та відсутності внутрішньої оптимізації, Map може бути значно повільнішим, ніж звичайний старий об’єкт JavaScript для деяких завдань.


1
На вашу думку, чи переважає тут семантика ефективності? Карти звучать ідеально, якщо вам потрібен словник, але важче прийняти повільний пошук. Чи не швидкий пошук усієї суті словників?
користувач2954463

3
Я б точно йти з простими старими об'єктами , якщо ви добре з 11 мільйонів пар ключ / значення , і не піклуватися про вже існуючих ключів , як toString, constructorі т.д. (тобто ключі вкрай малоймовірно , щоб зіткнутися з ними). З ними легше працювати - наприклад, приріст є obj[i] = (obj[i] || 0) + 1, тоді як з Mapтим, map.set(i, (map.get(i) || 0) + 1)що все ще не так вже й погано, але він просто показує, як все може стати безладно. Карти, безумовно, мають свої випадки використання, але часто це робитиме звичайний об’єкт.

1
Зверніть увагу , що ви можете позбутися від за замовчуванням toString, constructor( і т.д.) властивості об'єкта шляхом написання obj = Object.create(null)замість obj = {}.

18

Об'єкти можуть вести себе як словники, тому що Javascript динамічно набирається, що дозволяє додавати або видаляти властивості на об'єкт в будь-який час.

Але новий Map()функціонал набагато кращий, оскільки:

  • Це забезпечує get, set, hasі deleteметоди.
  • Приймає будь-який тип для клавіш замість просто рядків.
  • Забезпечує ітератор для простого for-ofвикористання та підтримує порядок результатів.
  • Не має крайових корпусів з прототипами та іншими властивостями, що з'являються під час ітерації чи копіювання.
  • Він підтримує мільйони предметів.
  • Це дуже швидко і постійно стає швидшим, оскільки двигуни javascript покращуються.

99% часу ви просто повинні використовувати Map(). Однак якщо ви використовуєте лише клавіші на основі рядків і потребуєте максимальної продуктивності читання, то об'єкти можуть бути кращим вибором.

Подробиця полягає в тому, що (майже всі) двигуни javascript збирають об'єкти до класів C ++ у фоновому режимі. Ці типи кешуються та використовуються повторно за їх "контуром", тому при створенні нового об'єкта з такими ж точними властивостями двигун повторно використовувати наявний фоновий клас. Шлях доступу для властивостей цих класів дуже оптимізований і набагато швидший, ніж пошук a Map().

Додавання або видалення властивості призводить до повторного складання кешованого резервного класу, тому використання об'єкта як словника з великою кількістю доповнень та видалень ключів відбувається дуже повільно, але зчитування та призначення існуючих ключів без зміни об'єкта дуже швидко.

Отже, якщо у вас є велике навантаження з читанням за один раз за допомогою рядкових клавіш, тоді використовуйте objectспеціалізований високоефективний словник, а для всього іншого використовуйте Map().


Об'єкт надає get set has deleteі т.д. функціональність, він просто не настільки елегантний (але і не поганий). Яким способом Mapпростіше використовувати для ітерації? Не впевнений, що можу погодитися.
Андрій

@Andrew Я використовую методи, і функціональність також відрізняється залежно від того, що ви використовуєте та результату. Ітерація простіша, оскільки прототип та нативні властивості не відображаються у циклі та використовує звичайний ітератор JS, який підтримує той самий порядок.
Мані Гандхем

11

На додаток до інших відповідей, я виявив, що Карти більш складні та багатослівні для роботи, ніж об’єкти.

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

Це важливо, оскільки коротший код швидше читається, прямо виразніше і краще зберігається в голові програміста .

Ще один аспект: оскільки set () повертає карту, а не значення, ланцюжок призначення не може бути.

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

Налагодження карт також більш болюче. Нижче ви фактично не бачите, які ключі є на карті. Для цього вам доведеться написати код.

Успіху оцінюючи Ітератор карт

Об'єкти можуть бути оцінені будь-яким IDE:

WebStorm, що оцінює об'єкт


4
Зважаючи на все це, схоже, що карта - це передчасна оптимізація.
PRMan

10

Підсумок:

  • Object: Структура даних, в якій дані зберігаються як пари ключових значень. У об’єкті ключем має бути число, рядок або символ. Значення може бути будь-яким, тому інші об'єкти, функції тощо. Об'єкт - це не упорядкована структура даних, тобто послідовність вставки пар ключових значень не запам'ятовується
  • ES6 Map: Структура даних, в якій дані зберігаються як пари ключових значень. У якому унікальний ключ відображає значення . І ключ, і значення можуть бути в будь-якому типі даних . Карта - це ітерабельна структура даних, це означає, що послідовність вставки запам'ятована і ми можемо отримати доступ до елементів, наприклад, for..ofциклу

Основні відмінності:

  • A Mapє впорядкованим і ітерабельним, тоді як об'єкти не впорядковані та не ітерабельні

  • Ми можемо поставити будь-який тип даних як Mapключ, тоді як об’єкти можуть мати лише число, рядок або символ як ключ.

  • А Mapуспадковує від Map.prototype. Це пропонує всілякі корисні функції та властивості, що значно спрощує роботу з Mapоб’єктами.

Приклад:

об’єкт:

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)

Карта:

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

джерело: MDN


4

Окрім того, що вони можуть бути ітерабельними у чітко визначеному порядку та можливість використовувати довільні значення як ключі (крім -0), карти можуть бути корисними з наступних причин:

  • Спеціалізація примушує операції з картою в середньому бути підрядними.

    Будь-яка нерозумна реалізація об'єкта використовує хеш-таблицю або подібне, тому пошукові властивості, ймовірно, будуть в середньому постійними. Тоді об’єкти могли бути навіть швидшими, ніж карти. Але цього не вимагає специфікація.

  • Об'єкти можуть мати неприємні несподівані поведінки.

    Наприклад, скажімо, що ви не встановили жодне fooвластивість для новоствореного об'єкта obj, тому ви очікуєте obj.fooповернення невизначеного. Але fooможе бути вбудована власність, успадкована від Object.prototype. Або ви намагаєтесь створити obj.fooза допомогою призначення, але якийсь сетер Object.prototypeпрацює, замість того, щоб зберігати ваше значення.

    Карти забороняють подібні речі. Ну, якщо не зіпсується якийсь сценарій Map.prototype. І Object.create(null)це теж працюватиме, але тоді ви втрачаєте простий синтаксис ініціалізатора об'єктів.


4

Коли використовувати Карти замість простих об’єктів JavaScript?

Простий об’єкт JavaScript {key: 'value'} містить структуровані дані. Але звичайний об'єкт JS має свої обмеження:

  1. Тільки рядки та символи можуть використовуватися як клавіші Об'єктів. Якщо ми використовуємо будь-які інші речі, наприклад, цифри як ключі об'єкта, то під час доступу до цих клавіш ми побачимо, що ці ключі будуть перетворені в рядки, що неявно призводять до втрати послідовності типів. const name = {1: 'один', 2: 'два'}; Object.keys (імена); // ['1', '2']

  2. Є ймовірність випадкового заміни успадкованих властивостей з прототипів, написавши JS-ідентифікатори як ключові назви об'єкта (наприклад, toString, конструктор тощо).

  3. Інший об'єкт не може бути використаний як ключ об'єкта, тому ніяка додаткова інформація не може бути записана для об'єкта, записавши цей об'єкт як ключ іншого об'єкта, а значення іншого об'єкта буде містити додаткову інформацію

  4. Об'єкти не є ітераторами

  5. Розмір об'єкта не може бути визначений безпосередньо

Ці обмеження Об'єктів вирішуються Картками, але ми повинні розглядати Карти як доповнення до Об'єктів замість заміни. В основному Map - це лише масив масивів, але ми повинні передавати цей масив масивів об’єкту Map як аргумент з новим ключовим словом, інакше лише для масиву масивів корисні властивості та методи Map недоступні. І пам’ятайте, що пари ключових значень всередині масиву масивів або Map мають бути розділені лише комами, а не колонами, як у звичайних об'єктах.

3 поради, щоб вирішити, чи використовувати карту чи об’єкт:

  1. Використовуйте карти над об'єктами, коли ключі до часу запуску невідомі, оскільки ключі, сформовані за допомогою введення користувача або несвідомо, можуть порушити код, який використовує об'єкт, якщо ці ключі перезаписати успадковані властивості об’єкта, тому карта в цих випадках безпечніша. Також використовуйте карти, коли всі клавіші одного типу і всі карти одного типу.

  2. Використовуйте карти, якщо є необхідність зберігати примітивні значення як ключі.

  3. Використовуйте об'єкти, якщо нам потрібно оперувати окремими елементами.

Перевагами використання Карт є:

1. Карта приймає будь-який тип ключа і зберігає тип ключа:

Ми знаємо, що якщо ключ об’єкта не є рядком чи символом, то JS неявно перетворює його в рядок. Навпаки, Map приймає будь-який тип ключів: рядок, число, булеве значення, символ тощо. Map зберігає початковий тип ключа. Тут ми будемо використовувати номер як ключ всередині карти, і він залишиться числом:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

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

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

Всередині карти ми навіть можемо використовувати цілий об’єкт як ключ. Можуть бути випадки, коли ми хочемо зберігати деякі пов’язані з об'єктом дані, не прикріплюючи ці дані всередині самого об’єкта, щоб ми могли працювати з худорлявими об'єктами, але хотіли б зберегти певну інформацію про об'єкт. У цих випадках нам потрібно використовувати Map, щоб ми могли зробити Object як ключові та пов'язані з ним дані об'єкта як значення.

const foo= {name: foo};

const bar= {name: bar};

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

Але недоліком цього підходу є складність доступу до значення за ключем, оскільки нам доведеться прокрутити весь масив, щоб отримати бажане значення.

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

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

Ми можемо вирішити цю проблему, не отримуючи прямого доступу до значення, використовуючи відповідну Map.

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'

Ми могли це зробити за допомогою WeakMap, просто треба написати, const myMap = new WeakMap (). Відмінності між Map і WeakMap полягають у тому, що WeakMap дозволяє збирати сміття ключів (тут об’єкти), тому він запобігає витоку пам'яті, WeakMap приймає лише об'єкти як ключі, а WeakMap зменшив набір методів.

2. Карта не обмежує ключові назви:

Для простих об'єктів JS ми можемо випадково перезаписати властивість, успадковану від прототипу, і це може бути небезпечно. Тут ми перезапишемо властивість toString () об'єкта актора:

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

Тепер давайте визначимо fn isPlainObject (), щоб визначити, чи поданий аргумент є простим об'єктом, і цей fn використовує метод toString () для перевірки:

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

Карта не має жодних обмежень щодо імен ключів, ми можемо використовувати імена ключових слів, наприклад, toString, конструктор тощо. Тут хоча об'єкт glumMap має властивість, назване toString, але метод toString (), успадкований від прототипу об’єкта акторMap, працює бездоганно.

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

Якщо у нас виникає ситуація, коли користувацьке введення створює ключі, тоді ми повинні взяти ці ключі всередині Map, а не простого об'єкта. Це пояснюється тим, що користувач може вибрати власне ім’я поля, наприклад, toString, конструктор тощо. Тоді такі імена ключів у простому об'єкті потенційно можуть порушити код, який згодом використовує цей об’єкт. Тож правильне рішення - прив’язати стан користувальницького інтерфейсу до карти, немає можливості зламати карту:

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

3. Карта є ітерабельною:

Для ітерації властивостей простого об'єкта нам потрібні Object.entries () або Object.keys (). Object.entries (plainObject) повертає масив пар ключових значень, витягнутих з об'єкта, ми можемо деструктурувати ці ключі та значення та можемо отримати нормальні ключі та значення виводу.

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

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

Оскільки Карти є ітерабельним, тому нам не потрібні методи entries () для повторення карти та деструктуризації ключа, масив значень може бути виконаний безпосередньо на карті, оскільки всередині карти кожен елемент живе як масив пар ключових значень, розділених комами .

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'

Також map.keys () повертає ітератор над ключами, а map.values ​​() повертає ітератор над значеннями.

4. Ми можемо легко знати розмір карти

Ми не можемо безпосередньо визначити кількість властивостей у простому об’єкті. Нам потрібен помічник fn, як Object.keys (), який повертає масив з ключами об'єкта, а потім, використовуючи властивість length, ми можемо отримати кількість клавіш або розмір простого об'єкта.

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

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

console.log(sizeOfObj);       // 2

Але у випадку Карт ми можемо мати прямий доступ до розміру Карти за допомогою властивості map.size.

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

console.log(examsMap.size);

1

Ці два поради допоможуть вам вирішити, чи використовувати карту чи об’єкт:

  • Використовуйте карти над об'єктами, коли ключі до часу запуску невідомі, і коли всі ключі одного типу і всі значення одного типу.

  • Використовуйте карти у випадку, якщо є необхідність зберігати примітивні значення як ключі, оскільки об'єкт розглядає кожну клавішу як рядок або її числове значення, булеве значення або будь-яке інше примітивне значення.

  • Використовуйте об'єкти, коли є логіка, яка діє на окремі елементи.

Джерело: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
Ці поради не виглядають особливо корисними, особливо, оскільки це, як правило, не легко розділити речі за цими критеріями. З першим я не розумію, чому карти - це користь, коли ключі / значення однотипні. Це звучить більше, як це намагається сказати використовувати об’єкти, такі як класи / структури, карти, як колекції. Другий написаний погано, не доходячи до суті. Це дійсно означає використовувати карти, коли ви змішали рядкові еквівалентні типи ("1" і 1) або коли вам потрібно / хочете зберегти ключові типи. Останнє, я думаю, це те саме, що і перше, це припущення, що ти не знаєш, що таке об’єкт, так що воно неясне.
jgmjgm

1

Це короткий спосіб, як я його пам’ятаю: KOI

  1. Ключі. Клавіша об'єкта - це рядки або символи. Клавішами на карті також можуть бути цифри (1 і "1" різні), об'єкти NaNтощо. Він використовує ===для розрізнення клавіш за одним винятком, NaN !== NaNале ви можете використовувати їх NaNяк ключ.
  2. Замовлення. Порядок вставки запам'ятовується. Так [...map]або [...map.keys()]має певний порядок.
  3. Інтерфейс. Об'єкт: obj[key]або obj.a(якоюсь мовою []і []=справді є частиною інтерфейсу). Карта має get(), set(), has(), і delete()т.д. Зверніть увагу , що ви можете використовувати , map[123]але який використовує його як простий об'єкт JS.

0

За словами Mozilla

Об’єкт vs Map у JavaScript коротко з прикладами.

Об'єкт - дотримується тієї ж концепції, що і карта, тобто використання пари ключ-значення для зберігання даних. Але є невеликі відмінності, що робить карту кращим виконавцем у певних ситуаціях.

Map - це структура даних, яка допомагає зберігати дані у вигляді пар. Пара складається з унікального ключа та значення, відображеного на ключ. Це допомагає запобігти подвійності.

Основні відмінності

  • Карта - це примірник об'єкта, але навпаки, це неправда.

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

  • В Object тип даних типу ключа-ключа обмежений цілим числом, рядками та символами. Якщо в Map, поле ключів може бути будь-якого типу даних (ціле число, масив, об'єкт)

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)

  • У карті збережено початковий порядок елементів. Це не відповідає дійсності об'єктів.

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


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.