Map vs Object в JavaScript

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

карта: объекты карты-это простые карты ключей / значений.

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

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

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

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

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

почему JavaScript получает (хорошо поддерживает)

7 ответов


согласно mozilla:

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

и

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

объект имеет прототип, поэтому на карте есть ключи по умолчанию. Однако это можно обойти с помощью map = Object.create(null). Этот ключи объекта-это строки, где они могут быть любым значением для карты. Вы можете легко получить размер карты, пока вам нужно вручную сохранить отслеживание размера объекта.

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

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

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

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

в myMap.has(key) метод будет особенно удобен, а также myMap.size собственность.


ключевое отличие состоит в том, что объекты поддерживают только строковые ключи, где as Maps поддерживает более или менее любой тип ключа.

если я делаю obj[123] = true, а затем Object.ключи (obj) тогда я получу ["123"], а не [123]. Карта сохранит тип ключа и вернет [123], что отлично. Карты также позволяют использовать объекты в качестве ключей. Традиционно для этого вам нужно будет дать объектам какой-то уникальный идентификатор, чтобы хэшировать их (я не думаю, что когда-либо видел что-то вроде getObjectId в JS как часть стандарта). Карты также гарантируют сохранение порядка, поэтому все вокруг лучше для сохранения и иногда может сэкономить вам нужно сделать несколько видов.

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

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

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

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

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

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

опять же эти цифры различаются, но в основном объект имеет хорошее лидерство. В некоторых случаях лидерство для объектов над картами является экстремальным (~10 раз лучше), но на в среднем это было примерно в 2-3 раза лучше. Кажется, экстремальные скачки производительности могут работать в обоих направлениях. Я тестировал это только в Chrome и создании профиля использования памяти и накладных расходов. Я был очень удивлен, увидев, что в Chrome карты с одним ключом используют примерно в 30 раз больше памяти, чем объекты с одним ключом.

для тестирования многих небольших объектов со всеми вышеуказанными операциями (4 ключа):

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

С точки зрения выделения памяти они вели себя одинаково с точки зрения освобождение / GC, но карта использовала в 5 раз больше памяти. Этот тест использовал 4 ключа, где, как и в последнем тесте, я установил только один ключ, поэтому это объяснило бы сокращение накладных расходов памяти. Я провел этот тест несколько раз, и карта/объект более или менее шеи и шеи в целом для Chrome с точки зрения общей скорости. В FireFox для небольших объектов есть определенное преимущество в производительности над картами в целом.

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

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


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


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

в chrome я могу получить 16.7 млн. пар ключ/значение с Map и 11.1 миллион с регулярным объектом. Почти ровно на 50% больше пар с Map. Они оба занимают около 2GB памяти перед сбоем, и поэтому я думаю, что это может быть связано с ограничением памяти chrome (редактировать: да, попробуйте заполнить 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 для некоторых задач.


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

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

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

Good luck evaluating a Map Iterator

объекты могут быть оценены любой IDE:

WebStorm evaluating an object


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

  • спец применяет карте операций сублинейных в среднем.

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

  • объекты могут иметь неприятные неожиданные поступки.

    например, предположим, что вы не установилиfoo свойство для вновь созданного объекта obj, Так что вы ожидаете obj.foo возвращает значение undefined. Но!--1--> может быть встроенным свойством, унаследованным от Object.prototype. Или вы пытаетесь создать obj.foo используя назначение, но некоторый сеттер в Object.prototype выполняется вместо сохранения вашего значения.

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


объекты могут вести себя как словари, потому что Javasript динамически типизирован (и до сих пор не было хорошей альтернативы), но они действительно не должны быть.

новая Map() функциональность намного приятнее, потому что он имеет ожидаемый get/set/has/delete методы, которые вы ожидаете, а также принимать любые типы ключей, а не только строки. Его проще использовать при итерации и не имеет граничных случаев с прототипами и другими свойствами. Это также очень быстро и продолжает ускоряться по мере улучшения двигателей. Для 99% сценариев, вы должны просто использовать Map().

однако, если вы используете только строковые ключи и нуждаетесь в максимальной производительности чтения, объекты могут помочь. Деталь заключается в том, что (почти все) движки javascript компилируют объекты вплоть до классов C++ в фоновом режиме. Эти типы обычно просматриваются контуром, что означает, что при создании нового объекта с теми же точными свойствами, что и существующий объект, механизм будет повторно использоваться существующий фоновый класс. Аналогично, путь доступа к свойствам в этих классах поддержки очень оптимизирован и намного быстрее, чем перенаправление метода и поиск Map().

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

Итак, если вы делаете много чтений, то посмотрите на object как специализированный высокопроизводительный словарь, но для всего остального используйте Map().


эти два совета могут помочь вам решить, использовать ли карту или объект:

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

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

  • использовать объекты, когда есть логика, которая работает на отдельных элементах.

источник: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared