Карта против объекта в JavaScript
Я только что обнаружил chromestatus.com и, потеряв несколько часов своего дня, нашел эту запись:
Карта: Объекты карты являются простыми картами ключ / значение.
Это смутило меня. Обычные объекты JavaScript являются словарями, так как Map
отличается от словаря? Концептуально они идентичны (в соответствии с " Чем отличается карта от словаря?")
Справочные материалы по chromestatus также не помогают:
Объекты карты представляют собой наборы пар ключ / значение, где и ключи, и значения могут быть произвольными значениями языка ECMAScript. Отдельное значение ключа может встречаться только в одной паре ключ / значение в коллекции карты. Определите ключевые значения в соответствии с алгоритмом сравнения, который выбирается при создании карты.
Объект Map может перебирать свои элементы в порядке вставки. Объект карты должен быть реализован с использованием хеш-таблиц или других механизмов, которые в среднем обеспечивают времена доступа, которые являются сублинейными по количеству элементов в коллекции. Структуры данных, используемые в этой спецификации объектов Map, предназначены только для описания требуемой наблюдаемой семантики объектов Map. Он не предназначен для жизнеспособной модели реализации.
... все еще звучит как объект для меня, так ясно, что я что-то пропустил.
Почему JavaScript получает (хорошо поддерживается) Map
объект? Что оно делает?
17 ответов
По словам Мозиллы:
Объект 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
имущество.
Основным отличием является то, что Объекты поддерживают только строковые ключи, тогда как Карты поддерживают более или менее любой тип ключа.
Если я сделаю obj[123] = true, а затем Object.keys(obj), тогда я получу ["123"], а не [123]. Карта сохранила бы тип ключа и вернула [123], что здорово. Карты также позволяют использовать объекты в качестве ключей. Традиционно для этого вам нужно было бы дать объектам какой-то уникальный идентификатор для их хеширования (я не думаю, что когда-либо видел что-то вроде getObjectId в JS как часть стандарта). Карты также гарантируют сохранение порядка, поэтому они лучше сохраняются и иногда могут спасти вас от необходимости делать несколько сортировок.
Между картами и объектами на практике очень много плюсов и минусов. Объекты получают как преимущества, так и недостатки, будучи очень тесно интегрированными в ядро JS, что отличает их от значительного 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, однако производительность и использование памяти мрачны, фактически создание собственного прокси-сервера, который выглядит так, как будто 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). Я не проверял это, но есть вероятность, что это сильно повредит производительности по сравнению с 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 для небольших объектов есть определенное преимущество в производительности по сравнению с картами в целом.
Это, конечно, не включает в себя отдельные варианты, которые могут сильно отличаться. Я бы не советовал микрооптимизировать с этими цифрами. Что из этого можно сделать, так это то, что, как правило, более тщательно рассмотрите Карты для хранилищ с очень большими значениями ключей и объекты для хранилищ с небольшими значениями ключей.
Кроме того, лучшая стратегия с этими двумя - это реализовать ее и просто заставить работать в первую очередь. При профилировании важно иметь в виду, что иногда вещи, о которых вы не думаете, будут медленными, если смотреть на них, они могут быть невероятно медленными из-за причуд движка, как это видно в случае удаления ключа объекта.
Объекты могут вести себя как словари, потому что Javasript динамически типизирован (и до сих пор не было хорошей альтернативы), но на самом деле они не предназначены для этого.
Новый Map()
функциональность намного приятнее, потому что она имеет ожидаемый get/set/has/delete
методы, которые вы ожидаете, при этом принимая ключи любого типа, а не только строки. Его проще использовать при итерации, и у него нет крайних случаев, когда появляются прототипы и другие свойства. Это также очень быстро и продолжает работать быстрее, так как двигатели становятся лучше. Для 99% сценариев вы должны просто использовать Map()
,
Однако, если вы используете только строковые ключи и вам нужна максимальная производительность чтения, тогда объекты могут помочь. Суть в том, что (почти все) движки JavaScript компилируют объекты до классов C++ в фоновом режиме. Эти типы обычно ищутся по схеме, то есть, когда вы создаете новый объект с такими же точными свойствами, что и у существующего объекта, движок будет повторно использовать существующий фоновый класс. Аналогично, путь доступа к свойствам в этих классах поддержки очень оптимизирован и намного быстрее, чем перенаправление и поиск метода Map()
,
Когда вы добавляете или удаляете свойство, класс поддержки и кэшированные пути поиска очищаются и перекомпилируются на лету, поэтому вы теряете производительность, когда используете объект в качестве словаря с большим количеством создания и удаления ключей, но читает и Назначение существующего ключа очень быстро.
Так что, если вы делаете много чтений, то посмотрите на object
в качестве специализированного высокопроизводительного словаря, но для всего остального используйте Map()
,
Резюме:
Object
: Структура данных, в которой данные хранятся в виде пар ключ-значение. В объекте ключ должен быть числом, строкой или символом. Значением может быть что угодно, так же как и другие объекты, функции и т. Д. Объект являетсянеупорядоченной структурой данных, т.е. последовательность вставки пар ключ-значение не запоминается.ES6 Map
: Структура данных, в которой данные хранятся в виде пар ключ-значение. В которомуникальный ключ сопоставляется со значением. И ключ, и значение могут быть влюбом типе данных. Карта - это итеративная структура данных, это означает, что последовательность вставки запоминается и что мы можем получить доступ к элементам, например, вfor..of
петля
Ключевые отличия:
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 () {}
Я не думаю, что следующие ответы были упомянуты в ответах до сих пор, и я подумал, что их стоит упомянуть.
Карты могут быть больше
В Chrome я могу получить 16,7 миллионов пар ключ / значение с Map
против 11,1 млн с обычным объектом. Почти ровно на 50% больше пар с Map
, Они оба занимают около 2 ГБ памяти, прежде чем они терпят крах, и поэтому я думаю, что может быть связано с ограничением памяти хромом (Edit: Yep, попробуйте заполнить 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 для некоторых задач.
Когда использовать Карты вместо простых объектов JavaScript?
Простой объект JavaScript { key: 'value' } содержит структурированные данные. Но простой объект JS имеет свои ограничения:
В качестве ключей Объектов можно использовать только строки и символы. Если мы будем использовать что-то еще, например, числа в качестве ключей объекта, тогда во время доступа к этим ключам мы увидим, что эти ключи будут преобразованы в строки, что неявно приведет к потере согласованности типов. const names= {1: 'один', 2: 'два'}; Object.keys(имена); // ['1', '2']
Есть вероятность случайной перезаписи унаследованных свойств от прототипов путем записи идентификаторов JS в качестве ключевых имен объекта (например, toString, конструктор и т. Д.)
Другой объект не может использоваться в качестве ключа объекта, поэтому для объекта нельзя записать дополнительную информацию, записав этот объект как ключ другого объекта, а значение этого другого объекта будет содержать дополнительную информацию.
Объекты не являются итераторами
Размер объекта невозможно определить напрямую
Эти ограничения объектов устраняются с помощью Maps, но мы должны рассматривать карты как дополнение к объектам, а не замену. В основном Map - это просто массив массивов, но мы должны передать этот массив массивов объекту Map в качестве аргумента с ключевым словом new, иначе только для массива массивов полезные свойства и методы Map недоступны. И помните, что пары ключ-значение внутри массива массивов или карты должны быть разделены только запятыми, а не двоеточиями, как в простых объектах.
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() объекта-актера:
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, constructor и т. Д. Здесь, хотя объект актера имеет свойство с именем toString, но метод toString(), унаследованный от прототипа объекта актера, работает отлично.
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);
В дополнение к другим ответам я обнаружил, что Карты более громоздкие и многословные для работы, чем объекты.
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:
Согласно Mozilla
Краткий обзор Object vs Map в 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)
Это короткий способ запомнить это: КОИ
- Ключи. Ключ объекта - это строки или символы. Ключи карты также могут быть числами (1 и "1" - разные), объектами,
NaN
и т.д. Он использует===
различать ключи, за одним исключениемNaN !== NaN
но вы можете использоватьNaN
как ключ. - Порядок. Запоминается порядок размещения. Так
[...map]
или[...map.keys()]
имеет особый порядок. - Интерфейс. Объект:
obj[key]
илиobj.a
(на каком-то языке,[]
а также[]=
действительно являются частью интерфейса). Карта имеетget()
,set()
,has()
,delete()
и т.д. Обратите внимание, что вы можете использоватьmap[123]
но он использует его как простой объект JS.
В дополнение к возможности повторения в четко определенном порядке и возможности использовать произвольные значения в качестве ключей (кроме -0
), карты могут быть полезны по следующим причинам:
Спецификация заставляет операции карты быть сублинейными в среднем.
Любая не глупая реализация объекта будет использовать хеш-таблицу или аналогичную, поэтому поиск свойств, вероятно, будет в среднем постоянным. Тогда объекты могут быть даже быстрее, чем карты. Но это не требуется спецификацией.
У объектов могут быть неприятные неожиданные поведения.
Например, скажем, вы не установили какой-либо
foo
свойство недавно созданного объектаobj
так что вы ожидаетеobj.foo
вернуть неопределенное. Ноfoo
может быть встроенным свойством, унаследованным отObject.prototype
, Или вы пытаетесь создатьobj.foo
используя назначение, но какой-то сеттер вObject.prototype
работает вместо сохранения вашей ценности.Карты предотвращают подобные вещи. Ну, если какой-то скрипт не испортит
Map.prototype
, А такжеObject.create(null)
тоже будет работать, но тогда вы потеряете простой синтаксис инициализатора объекта.
Я наткнулся this post
Минко Гечева, который четко объясняет основные различия.
Я запустил стенд и обнаружил, что объекты работают намного быстрее, даже по сравнению с картой с цифровыми клавишами.
// Create a map with 1000 entries, and an array of keys (shuffled)
const indexes = new Array(1000).fill(0).map((_, i) => i)
const map = indexes.reduce((o, k) => {
o[k] = k
return o
}, {})
const es6Map = new Map(indexes.map(v => [v, v]))
indexes.sort(() => Math.random() - 0.5);
Затем тесты произвольного доступа на обоих:
for (let i = 0; i < 1000; i++) {
if (map[i] === 999) {
console.log('gotcha')
}
}
for (let i = 0; i < 1000; i++) {
if (es6Map.get(i) === 999) {
console.log('gotcha')
}
}
Результаты на jsbench.me (Chrome 113 Mac M1 Max)
Объект: 218 тыс. операций в секунду ± 3,84% Самый быстрый
Карта: 95 тыс. операций в секунду ± 1,62 %, медленнее на 56,39 %.
Один из аспектов карты, который здесь не особо обсуждается, - это поиск. Согласно спецификации:
Объект Map должен быть реализован с использованием либо хэш-таблиц, либо других механизмов, которые в среднем обеспечивают время доступа, сублинейно зависящее от количества элементов в коллекции. Структуры данных, используемые в этой спецификации объектов карты, предназначены только для описания требуемой наблюдаемой семантики объектов карты. Он не предназначен для использования в качестве жизнеспособной модели реализации.
Для коллекций, содержащих огромное количество элементов и требующих поиска элементов, это огромный прирост производительности.
TL;DR - поиск объекта не указан, поэтому он может быть в порядке количества элементов в объекте, то есть O(n). Поиск по карте должен использовать хеш-таблицу или что-то подобное, поэтому поиск по карте одинаков независимо от размера карты, то есть O(1).
Здесь так много ответов, но никто не выходит и не говорит самую важную часть ответа, а именно то, что большую часть времени вы хотите...
ПРИДЕРЖИВАЙТЕСЬ ОБЪЕКТОМ!
может быть лучше, если :
- вы хотите использовать нестроковые ключи (не то, что вам обычно нужно, но в некоторых случаях это может быть полезно)
- вы действительно заботитесь о порядке ключей в нескольких средах (в большинстве сред порядок ключей на объектах сохраняется движком JS, но это не гарантируется спецификацией. Итак, если вам действительно нужно быть уверенным, что ваш порядок ключей будет сохранен, используйте а)
- у вас есть очень большие наборы данных (мы говорим о том, что более 99,9% интерфейсов JS когда-либо будут использовать во всем своем приложении ... но в других ситуациях, когда обрабатываются огромные объемы данных, оба могут хранить больше данные, чем объект, и потенциально могут получить доступ к этим данным быстрее... но если у вас нет большого количества данных, это преждевременная оптимизация)
- вы в основном зацикливаетесь (обычные старые объекты имеют лучший интерфейс для большинства вещей, но для их зацикливания вам нужно преобразовать их в массив с
Object.keys
;Map
s можно повторять напрямую)
Эти два совета могут помочь вам решить, использовать ли карту или объект:
Используйте карты над объектами, когда ключи неизвестны до времени выполнения, и когда все ключи имеют одинаковый тип и все значения имеют одинаковый тип.
Используйте карты в случае, если необходимо хранить значения примитивов в качестве ключей, поскольку объект обрабатывает каждый ключ как строку, либо его числовое значение, либо логическое значение, либо любое другое примитивное значение.
Используйте объекты, когда есть логика, которая работает с отдельными элементами.
Источник: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections
Вкратце: вы не можете использовать ключ length для хранения значения в объекте, но можете использовать его в Map.
Практическое соображение, достаточное для меня, чтобы не использовать объект и использоватьMap
вместо этого тот факт, что для практических целей ключ объекта - это не строка или число, а скорее подмножество строк или числа. В частности, использование ключа, который конфликтует с именем свойства прототипа или хорошо известным свойством, может вызвать проблему. Например, сохранение значения с помощью ключа может запутать код, использующий наличиеlength
ключ, чтобы определить, является ли данный объект массивом.
В дополнение к упомянутым выше различиям в удобстве использования, если вас больше интересует разница в производительности на огромных объектах, простой объект кажется примерно в 2 раза быстрее в хроме при настройке, обновлении и удалении большого объема данных.