Map vs Object: почему ключи-объекты становятся [object Object] и крашат кэш сессий
-
Map и Object кажутся похожими, но ключи-объекты в Object превращаются в “[object Object]” и затирают данные. Это бьет по кэшу сессий, когда userId как объект улетает в никуда. Разберем под капотом, почему так происходит и как фиксить без костылей.
В сессионном кэше часто хранят данные по объектным ключам - типа {userId: {data}}. Object их строкует, и разные объекты сливаются в одну строку. Map держит ссылки как есть. Это спасает от коллизий в реальных проектах с авторизацией и state.
Как Object ломает ключи-объекты
Object в JS - это не коллекция, а хэш-таблица со строковыми ключами. Любое значение, не строка или Symbol, преобразуется через toString(). Объект по умолчанию дает “[object Object]”, функция - “function …()”. Поэтому два разных userId-объекта сливаются в одну запись.
Представь кэш сессий: генеришь уникальный объект для пользователя, кладешь данные. Следующий запрос - новый объект, но ключ строкует в то же “[object Object]”. Старые данные перезаписываются. Краш: сессия теряет состояние, пользователь видит чужие данные или пустоту.
Вот классическая засада:
- Создаешь
const cache = {}; const user1 = {id: 123}; cache[user1] = 'session1';const user2 = {id: 123}; cache[user2] = 'session2';console.log(cache[user1]); // 'session2'- коллизия!
Реальный пример из сессий:
const sessionCache = {}; function getSession(userObj) { return sessionCache[userObj] || 'new'; } const u1 = {id: 1}; const u2 = {id: 1}; sessionCache[u1] = 'user1 data'; console.log(getSession(u2)); // 'user1 data' - но должно быть undefinedНюанс: Даже если объекты “равны” по содержимому, ссылки разные - Object их не различает после toString().
Map держит ссылки без принуждения
Map - настоящая коллекция ключ-значение без строкования. Ключ остается объектом, сравнивается по ссылке через ===. Размер через .size, итерация через for…of. Идеально для динамических ключей в кэше.
В сессиях: генерируешь объект-ключ один раз на userId, держишь в Map. Последующие запросы используют тот же объект или WeakMap для GC. Нет коллизий, производительность на частых set/get выше Object при >100 записях.
Перепишем кэш:
const sessionCache = new Map(); const u1 = {id: 1}; const u2 = {id: 1}; sessionCache.set(u1, 'user1 data'); console.log(sessionCache.get(u2)); // undefined - ок! console.log(sessionCache.size); // 1Преимущества Map для кэша сессий:
- Ключи любого типа: объекты, функции, Date.
- .has(key), .delete(key) - O(1) без Object.keys().
- Итераторы keys(), values(), entries() - чище Object.entries().
Свойство Object Map Ключи строки/Symbol любой тип Размер Object.keys().length .size Get/Set obj[key] .get/.set Итерация for…in (прототип!) for…of чисто Кэш сессий коллизии [object Object] ссылки OK WeakMap для сессий без утечек памяти
Обычный Map держит ключи сильно, объект-ключ не удалится GC пока Map жив. Для сессий - WeakMap: ключи слабо ссылаются, память чистится автоматически при удалении userObj.
В бэкенде/фронте: хранишь сессию по DOM-элементу или Worker’у как ключ. Когда элемент удален - сессия уходит без .delete. Идеально для временного кэша без ручной уборки.
Практика:
const sessionWeakCache = new WeakMap(); const userObj = {id: 1}; sessionWeakCache.set(userObj, 'session data'); console.log(sessionWeakCache.get(userObj)); // 'session data' // userObj = null; // ключ удалится GCКогда WeakMap рулит:
- Ключи - DOM-ноды, объекты от API без ID.
- Нет .size, .keys() - только get/set/has/delete.
- Нет сериализации в JSON - только для runtime кэша.
Подводный камень: WeakMap не итерируется, нельзя Object.fromEntries(map).
Фиксим legacy-код: от Object к Map
В старом коде cache на Object. Миграция: new Map(Object.entries(cacheOld)), но только если ключи строковые! Для объектных - рефактори ключ на stringId или Symbol.
Гибрид: публичный API оставь Object, внутри Map. Конвертируй через Object.fromEntries(map.entries()) - но проверяй ключи на объекты.
Шаги рефакторинга:
- Замени
const cache = {}наnew Map(). .set(key, val)вместоcache[key] = val..get(key)вместоcache[key]. Добавь|| default.- Для JSON -
Object.fromEntries(cache).
Антипаттерны избегать:
JSON.stringify(key)как хэш - коллизии по содержимому.- Массивы как ключи в Object - то же [object Array].
- Глобальный cache без WeakMap - утечки памяти.
Под капотом еще есть грабли
Object прототип наследуется, Map чистый. В Object for…in ловит Object.prototype - фильтруй hasOwnProperty. Map итерируется только свои записи.
Осталось: производительность на 10k+ элементов (Map быстрее), полифиллы для старых браузеров. Подумай, стоит ли WeakRef в связке с Map для продвинутого GC в сессиях - там свои подводные камни с timing’ом очистки.
- Создаешь
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.