> Какие структуры данных использовать для хранения параметров и результатов мемоизации в JavaScript (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Домклик

Стек: JavaScript

> Пример ответа

Короткий ответ

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

Подробное объяснение

Выбор структуры данных зависит от типа ключей и жизненного цикла данных:

  • Обычный объект ({}) - подходит для примитивных ключей (строки, числа). Недостатки: прототипное наследование может привести к коллизиям (например, ключ "constructor"), и нет гарантии порядка ключей. Требует преобразования ключей в строку.
  • Map - универсальный выбор. Принимает любые значения как ключи (объекты, функции, примитивы), сохраняет порядок вставки, имеет удобные методы (has, get, set, delete). Итерация по ключам проще, чем у объекта.
  • WeakMap - ключи только объекты (не примитивы). Главное преимущество - слабые ссылки: если на объект-ключ нет других ссылок, запись удаляется сборщиком мусора. Это критично для мемоизации, когда ключи - временные объекты (например, аргументы-объекты в рекурсивных вызовах).
  • Вложенные структуры - для функций с несколькими аргументами можно использовать Map внутри Map (по одному уровню на аргумент). Это избегает сериализации и сохраняет ссылочную идентичность объектов.
  • Сериализация в строку - JSON.stringify или кастомный хэш для комбинации аргументов. Быстро для простых примитивов, но не работает для объектов с циклическими ссылками и теряет ссылочную идентичность (два разных объекта с одинаковым содержимым будут считаться одним ключом).

Для кэширования с ограничением размера (LRU-кэш) Map удобен, так как порядок вставки позволяет легко удалять самые старые записи.

На практике

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

Пример кода

JAVASCRIPT
// Мемоизация с одним объектным аргументом
function memoizeWeak(fn) {
const cache = new WeakMap();
return function(obj) {
if (cache.has(obj)) return cache.get(obj);
const result = fn(obj);
cache.set(obj, result);
return result;
};
}
// Мемоизация с несколькими примитивными аргументами через Map
function memoizePrimitive(fn) {
const cache = new Map();
return function(...args) {
const key = args.join('|');
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// Мемоизация с несколькими объектными аргументами через вложенные Map
function memoizeNested(fn) {
const cache = new Map();
return function(...args) {
let current = cache;
for (let i = 0; i < args.length - 1; i++) {
if (!current.has(args[i])) current.set(args[i], new Map());
current = current.get(args[i]);
}
const lastKey = args[args.length - 1];
if (current.has(lastKey)) return current.get(lastKey);
const result = fn(...args);
current.set(lastKey, result);
return result;
};
}

Как отвечать на собеседовании

Начните с главного: для мемоизации ключевой выбор - между Map и WeakMap. Объясните, что WeakMap решает проблему утечек памяти для объектных ключей. Упомяните ограничение WeakMap - только объекты. Затем добавьте про Map для примитивов и про вложенные структуры для множественных аргументов. Если спросят про производительность, отметьте, что Map быстрее объекта при частых операциях добавления/удаления. Приведите пример с рекурсивной функцией (например, вычисление чисел Фибоначчи), где WeakMap спасает от накопления мусора.

Что проверяет интервьюер

Интервьюер оценивает понимание:

  • различий между Map, WeakMap и обычным объектом;
  • механизма сборки мусора и слабых ссылок;
  • trade-off между сериализацией и сохранением ссылочной идентичности;
  • умения выбирать структуру под конкретный сценарий (тип ключей, время жизни кэша);
  • знания ограничений (например, WeakMap не итерируем, нет size).

Типичные ошибки

  • Использование обычного объекта без проверки hasOwnProperty - коллизии с прототипом.
  • Применение WeakMap для примитивных ключей - это вызовет ошибку.
  • Игнорирование утечек памяти: Map с объектными ключами без удаления записей.
  • Сериализация через JSON.stringify для объектов - потеря идентичности и проблемы с циклическими ссылками.
  • Неучёт того, что WeakMap не имеет методов size, clear и не итерируется - это ограничивает его использование для LRU-кэша.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью