Карта против объекта в JavaScript


290

Я только что обнаружил chromestatus.com и, потеряв несколько часов своего дня, нашел эту запись :

Карта: Объекты карты являются простыми картами ключ / значение.

Это смутило меня. Обычные объекты JavaScript являются словарями, так чем же они Mapотличаются от словаря? Концептуально они идентичны (в зависимости от разницы между картой и словарем? )

Ссылки на chromestatus в документации тоже не помогают:

Объекты карты представляют собой наборы пар ключ / значение, где и ключи, и значения могут быть произвольными значениями языка ECMAScript. Отдельное значение ключа может встречаться только в одной паре ключ / значение в коллекции карты. Определите ключевые значения в соответствии с алгоритмом сравнения, который выбирается при создании карты.

Объект Map может перебирать свои элементы в порядке вставки. Объект карты должен быть реализован с использованием хеш-таблиц или других механизмов, которые в среднем обеспечивают времена доступа, которые являются сублинейными по количеству элементов в коллекции. Структуры данных, используемые в этой спецификации объектов Map, предназначены только для описания требуемой наблюдаемой семантики объектов Map. Он не предназначен для жизнеспособной модели реализации.

... все еще звучит как объект для меня, так ясно, что я что-то пропустил.

Почему JavaScript получает (хорошо поддерживаемый) Mapобъект? Что оно делает?


Ответы:


286

По словам Мозиллы:

Объект Map может выполнять итерацию своих элементов в порядке вставки - цикл for..of будет возвращать массив [key, value] для каждой итерации.

и

Объекты похожи на Карты в том, что оба позволяют устанавливать ключи к значениям, извлекать эти значения, удалять ключи и определять, хранится ли что-то в ключе. Из-за этого Объекты исторически использовались как Карты; однако между объектами и картами есть важные различия, которые облегчают использование карты.

Объект имеет прототип, поэтому на карте есть ключи по умолчанию. Однако это можно обойти, используя 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.1KB, похоже, постоянные издержки. Если вместо этого вы создаете миллион карт с одним ключом и сравниваете его, он намного больше объекта.
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, что отличает их от значительного Map за исключением различий в поддержке ключей.

Непосредственным преимуществом является то, что у вас есть синтаксическая поддержка объектов, облегчающая доступ к элементам. У вас также есть прямая поддержка для этого с 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();

Есть несколько других вариантов, подходов, методологий и т. Д. С различными подъемами и падениями (производительность, краткость, переносимость, расширение и т. Д.). Объекты немного странны, так как являются ядром языка, поэтому у вас есть много статических методов для работы с ними.

Помимо преимущества Карт, сохраняющих типы ключей, а также возможность поддерживать такие вещи, как объекты, в качестве ключей, они изолированы от побочных эффектов, которые имеют многие объекты. Карта - это чистый хеш, и нет никакой путаницы в попытках быть объектом одновременно. Карты также могут быть легко расширены с помощью функций прокси. У объекта в настоящее время есть класс Proxy, однако производительность и использование памяти мрачны, фактически создание собственного прокси-сервера, который выглядит так, как будто карта для объектов в настоящее время работает лучше, чем 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). Я не проверял это, но есть вероятность, что это сильно повредит производительности по сравнению с stringify.

У других языков сценариев часто нет таких проблем, поскольку они имеют явные нескалярные типы для Map, Object и Array. Веб-разработка часто представляет собой проблему с нескалярными типами, когда вам приходится иметь дело с такими вещами, как PHP объединяет массив / карту с объектом, используя A / M для свойств, а JS объединяет карту / объект с массивом, расширяющим 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 // {}

Опять же, эти цифры меняются, но в основном у Object хорошее преимущество. В некоторых случаях опережение «Объекты над картами» является экстремальным (~ в 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 ключа, где, как и в последнем тесте, я установил только один ключ, так что это объясняет сокращение накладных расходов памяти. Я провел этот тест несколько раз, и карта / объект в целом более или менее похожи на Chrome с точки зрения общей скорости. В Firefox для небольших объектов есть определенное преимущество в производительности по сравнению с картами в целом.

Это, конечно, не включает в себя отдельные варианты, которые могут сильно отличаться. Я бы не советовал микрооптимизировать с этими цифрами. Что из этого можно сделать, так это то, что, как правило, более тщательно рассмотрите Карты для хранилищ с очень большими значениями ключей и объекты для хранилищ с небольшими значениями ключей.

Кроме того, лучшая стратегия с этими двумя - это реализовать ее и просто заставить работать в первую очередь. При профилировании важно иметь в виду, что иногда вещи, о которых вы не думаете, будут медленными, если смотреть на них, они могут быть невероятно медленными из-за причуд движка, как это видно в случае удаления ключа объекта.


Отсутствие сериализуемости стало настоящей болью для многих разработчиков. Посмотрите на комментарий Как мне сохранить карту ES6 в локальном хранилище (или в другом месте)? и как вы JSON.stringify ES6 Map? ,
Франклин Ю

Это число в миллисекундах, байтах или общем количестве объектов?
StefansArya

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

27

Я не думаю, что следующие ответы были упомянуты в ответах до сих пор, и я подумал, что их стоит упомянуть.


Карты могут быть больше

В хроме я могу получить 16,7 миллиона пар ключ / значение с MapVS. 11,1 млн с обычным объектом. Почти ровно на 50% больше пар с 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
Как вы думаете, семантика перевешивает производительность здесь? Карты звучат идеально, если вам нужен словарь, но трудно принять более медленный поиск. Разве быстрый поиск не является смыслом словарей?
user2954463

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 ++ в фоновом режиме. Эти типы кэшируются и повторно используются их «контуром», поэтому при создании нового объекта с точно такими же свойствами движок будет повторно использовать существующий фоновый класс. Путь доступа к свойствам в этих классах очень оптимизирован и намного быстрее, чем поиск Map().

Добавление или удаление свойства приводит к повторной компиляции кэшированного класса поддержки, поэтому использование объекта в качестве словаря с большим количеством добавлений и удалений ключей происходит очень медленно, но чтение и назначение существующих ключей без изменения объекта выполняется очень быстро.

Так что, если у вас нагрузка для чтения с однократной записью и строковыми ключами, используйте objectспециализированный высокопроизводительный словарь, а для всего остального используйте a 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вернуть undefined. Но fooможет быть встроенным свойством, унаследованным от Object.prototype. Или вы пытаетесь создать obj.fooс помощью присваивания, но Object.prototypeзапускается какой-то сеттер вместо сохранения вашего значения.

    Карты предотвращают подобные вещи. Ну, если какой-то сценарий не испортится Map.prototype. И Object.create(null)будет работать, но тогда вы потеряете простой синтаксис инициализатора объекта.


4

Когда использовать Карты вместо простых объектов JavaScript?

Простой объект JavaScript {key: 'value'} содержит структурированные данные. Но простой объект JS имеет свои ограничения:

  1. В качестве ключей Объектов могут использоваться только строки и символы. Если мы будем использовать любые другие вещи, скажем, числа в качестве ключей объекта, то при доступе к этим ключам мы увидим, что эти ключи будут неявно преобразованы в строки, что приведет к потере согласованности типов. const names = {1: «один», 2: «два»}; Object.keys (имена); // ['1', '2']

  2. Есть вероятность случайной перезаписи унаследованных свойств из прототипов путем записи идентификаторов JS в качестве ключевых имен объекта (например, toString, конструктор и т. Д.)

  3. Другой объект не может быть использован в качестве ключа объекта, поэтому для объекта нельзя записать дополнительную информацию, записав этот объект в качестве ключа другого объекта, и значение этого другого объекта будет содержать дополнительную информацию.

  4. Объекты не являются итераторами

  5. Размер объекта не может быть определен напрямую

Эти ограничения Объектов решаются с помощью Карт, но мы должны рассматривать Карты как дополнение к Объектам вместо замены. По сути, 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'

Мы можем решить эту проблему, не получая прямой доступ к значению, используя правильную карту.

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 () объекта actor:

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, конструктор и т. Д. Здесь, хотя объект actorMap имеет свойство с именем toString, но метод toString (), унаследованный от прототипа объекта actorMap, работает отлично.

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

Если у нас есть ситуация, когда пользовательский ввод создает ключи, тогда мы должны взять эти ключи внутри карты вместо простого объекта. Это связано с тем, что пользователь может выбрать имя настраиваемого поля, например, 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'

Поскольку карты являются итеративными, поэтому нам не нужны методы entry () для итерации по карте и деструктуризации ключа, массив значений может быть выполнен непосредственно на карте, поскольку внутри карты каждый элемент живет как массив пар значений ключа, разделенных запятыми. ,

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

По словам Мозиллы

Коротко об объекте против карты в JavaScript с примерами.

Объект - следует той же концепции, что и карта, т.е. использует пару ключ-значение для хранения данных. Но есть небольшие отличия, которые делают карту более эффективной в определенных ситуациях.

Карта - это структура данных, которая помогает хранить данные в виде пар. Пара состоит из уникального ключа и значения, сопоставленного с ключом. Это помогает предотвратить двуличие.

Ключевые отличия

  • Карта является экземпляром объекта, но, наоборот, не соответствует действительности.

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.