В чем отличие объектов от Map
Читаю про карты (Map) и не могу сообразить чем они отличаться от объектов, может вопрос странный, но я только начала изучать JavaScript и мне не сообразить - они ведь похожи, ведь так?
Это одно и тоже, или как?
Ответы (3 шт):
Если я не ошибаюсь, есть два главных различия:
Ключами у объектов могут быть только строки и символы (Symbols). При попытке использовать другие типы данных они приводятся к типу строки. У Map в качестве ключей могут быть любые типы данных, включая объекты.
У объектов есть прототипное наследование. То есть некоторые ключи у них уже предустановлены в прототипах. Это может привести к путанице и даже сломать программу, если вы используете объект в роли словаря — то есть структуры данных с любыми непредсказуемыми ключами, возникающими во время исполнения программы. У Map этого нет, они всегда пусты изначально, и если добавлять к ним элементы через их API, не возникает никакого риска что-то сломать или получить непредсказуемое поведение.
Есть и другие различия. Например, объекты легче сериализовать, хранить, передавать и восстанавливать (при помощи JSON) и они лучше подходят для создания элементов с определённой логикой и поведением (у объектов есть методы и внутренняя связь между методами и данными через this). Для Map легче получать размер структуры, они поддерживают итерацию без дополнительных усилий.
Структура данных 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`);