В чем отличие объектов от Map

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

Это одно и тоже, или как?


Ответы (3 шт):

Автор решения: vsemozhebuty

Если я не ошибаюсь, есть два главных различия:

  1. Ключами у объектов могут быть только строки и символы (Symbols). При попытке использовать другие типы данных они приводятся к типу строки. У Map в качестве ключей могут быть любые типы данных, включая объекты.

  2. У объектов есть прототипное наследование. То есть некоторые ключи у них уже предустановлены в прототипах. Это может привести к путанице и даже сломать программу, если вы используете объект в роли словаря — то есть структуры данных с любыми непредсказуемыми ключами, возникающими во время исполнения программы. У Map этого нет, они всегда пусты изначально, и если добавлять к ним элементы через их API, не возникает никакого риска что-то сломать или получить непредсказуемое поведение.

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

→ Ссылка
Автор решения: Vasily

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

Рассмотрим подробнее, создав из объекта простую карту:

const object = {
   email: "[email protected]", 
   url: "https://example.com"
}

const entries = Object.entries(object)

console.log(entries) 
// [ ["email", "[email protected]"], ["url", "https://example.com"] ]

const map = new Map(entries)

console.log(map) 
// Map { "email" => "[email protected]", "url" => "https://example.com" }

Карты перебираемы прямо "из коробки", для них определен протокол перебора, поэтому мы можем делать так:

for (let entry of map) {
  console.log(entry) 
  // ["email", "[email protected]"] 
  // ["url", "https://example.com"]
}

и нам даже не нужно отдельно применять метод entries() к map, так как он будет применен по умолчанию.

Если воспользоваться деструктуризацией, то переборка становиться еще удобнее:

for (let [key, value] of map) {
  console.log(key, value) 
  // email, [email protected] 
  // url, https://example.com
}

только по ключам:

for (let key of map.keys()) {
  console.log(key) 
  // email 
  // url
}

только по значениям:

for (let value of map.values()) {
  console.log(value) 
  // [email protected] 
  // https://example.com
}

У карт присутствую удобные встроенные методы и свойства для работы с ними, например такие как size, который позволяет узнать размер конкретной карты:

const size = map.size
console.log(size)
// 2

метод get() вернет нужное значения по ключу или undefined, если такого ключа нет в карте:

const email = map.get("email")
console.log(email)
// [email protected]

const address = map.get("address")
console.log(address)
// undefined

метод set(), для изменения или добавления новой пары:

map.set("email", "[email protected]")
console.log(map.get("email"))
// [email protected]

map.set("country", "Russia")
console.log(map.get("country"))
// Russia
console.log(map.size)
// 3

а методом has() можно проверить нужный ключ на наличие:

const hasEmail = map.has("email")
console.log(hasEmail)
// true

const hasAddress = map.has("address")
console.log(hasAddress)
// false

полный список методов можно посмотреть здесь.


Ключ в картах может быть не только строкой или символом (начиная с ECMAScript 2015), а вообще любым типом данных:

map.set(undefined, undefined)
map.set(NaN, NaN)
map.set(false, true)
map.set(object, "мой ключ это объект")
console.log(map)
// Map {
//    "email" => "[email protected]",
//    "url" => "https://example.com",
//    "country" => "Russia",
//    undefined => undefined,
//    NaN => NaN,
//    false => true,
//    { email: "[email protected]", url: "https://example.com" } => "мой ключ это объект"
// }

т.е если мы захотим получить значение по ключу object, то нам нужно будет передать в get() тот же объект:

const objectValue = map.get(object)
console.log(objectValue)
// мой ключ это объект

Воспользуемся методом класса Object fromEntries() что бы превратить нашу карту обратно в объект:

const mapToObject = Object.fromEntries(map)
console.log(mapToObject)    
// {
//   email: "[email protected]",
//   url: "https://example.com",
//   country: "Russia",
//   undefined: undefined,
//   NaN: NaN,
//   false: true,
//   "[object Object]": "мой ключ это объект"
// }

// JavaScript попытался привести последний ключ к строке 
// но поскольку он являлся объектом то мы получили "[object Object]"

Карты сохраняют порядок при добавлении новых элементов (см. пример консоли выше).


А их использование требует чуть большего количества памяти.

→ Ссылка
Автор решения: Олег Максимов

как бы там ни было, но Map примерно в 2 раза медленнее чем объект. Вот пример для тестирования этой особенности:

animals = [];
zooparks = [];
const animan_names = ['bobik', 'dino', 'monstr', 'dolgovaz'];
const sec = ['borisa', 'egora', 'vasilija'];
const zoo = ['len', 'mos', 'led', 'nsk', 'lol']
for(var i = 0; i <2000000; i++) {
    const animal = {
        id: i,
        name: animan_names[Math.floor(Math.random()*animan_names.length)] + ' ' + sec[Math.floor(Math.random()*sec.length)],
        zoopark_id: Math.floor(Math.random()*1000)
    }
    animals.push(animal);
    zooparks.push({
        id: i,
        name: zoo[Math.floor(Math.random()*zoo.length)]
    })
}
const start2 = new Date().getTime();
const zooMap2 = {};
zooparks.map(item => {zooMap2[item.id] =item.name});
for (let i = 0; i < animals.length; i++) {
    let k = ``+animals[i].name+' '+zooMap2[animals[i].zoopark_id];
}
const end2 = new Date().getTime();
console.log(`SecondWay: ${end2 - start2}ms`);

const start = new Date().getTime();
const zooMap = new Map();
zooparks.map(item => {zooMap.set(item.id, item.name)});
for (let i = 0; i < animals.length; i++) {
    let k = ``+animals[i].name+' '+zooMap.get(animals[i].zoopark_id);
}
const end = new Date().getTime();
console.log(`SecondWay: ${end - start}ms`);
→ Ссылка