Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Согласно mozilla:

Объект 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.

Ответ 2

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

Если я делаю obj [123] = true, а затем Object.keys(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.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.

Существенным недостатком для Maps является то, что они не поддерживаются напрямую 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 with 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 // {}

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

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

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

Ответ 3

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


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

В chrome я могу получить 16.7 миллионов пар ключ/значение с Map против 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 для некоторых задач.

Ответ 4

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

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

Ответ 5

Резюме:

  • 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)

Ответ 6

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

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

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

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

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

Ответ 7

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

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

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

  • У объектов может быть неприятное неожиданное поведение.

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

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

Ответ 8

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

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

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

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

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