Как реализовать функцию memo для кеширования результатов вызовов по аргументам в JavaScript
Что проверяют
Замыкания, выбор ключа для кеша и понимание, где мемоизация ломается. Следом спрашивают, что будет с объектами в аргументах и как ограничить рост памяти, иногда просят сравнить с useMemo и React.memo, которые решают другую задачу: пропуск перерисовки, а не кеш по всем аргументам.
Как отвечать
Каркас: в замыкании живёт Map, обёртка строит ключ, проверяет cache.has(key), при промахе вызывает fn.apply(this, args), сохраняет результат и возвращает его. Дальше разговор о ключе:
- один примитивный аргумент: сам аргумент как ключ
Map, без сериализации.Mapразличает1и'1', ключи обычного объекта нет; - несколько примитивов:
JSON.stringify(args)годится для чисел и строк, ноundefinedиNaNв массиве превращаются вnull, функции теряются,MapиSetсериализуются в{}, а объекты с разным порядком ключей дают разные строки; - объекты: ключ по ссылке через
WeakMap, тогда запись исчезнет вместе с объектом и не удержит его в памяти. Для нескольких аргументов строят дерево вложенныхMap, по уровню на аргумент; - размер: лимит записей с вытеснением старых.
Mapхранит порядок вставки, поэтому простой LRU пишется в несколько строк: при попадании удалить ключ и вставить заново, при переполнении удалитьcache.keys().next().value; - асинхронные функции: кешируется промис, а при его отклонении запись удаляется, иначе ошибка останется в кеше навсегда.
Отдельно скажите, что мемоизировать можно только чистые функции: результат должен зависеть лишь от аргументов.
Где спотыкаются
Проверяют кеш через if (cache[key]). Результаты 0, '', false и null при этом не находятся, и функция каждый раз считает заново. Нужна проверка наличия ключа: has у Map или key in cache у объекта.
Теряют контекст: fn(...args) вместо fn.apply(this, args). Мемоизированный метод объекта начинает падать на обращении к this.
Мемоизируют всё подряд. Кеш без лимита на долгоживущей странице превращается в утечку памяти, а JSON.stringify большого объекта стоит дороже, чем само вычисление, которое пытались сэкономить.