Как определить, что результат функции не изменился при повторном вызове для мемоизации
Что проверяют
Понимаете ли вы, что мемоизация сравнивает не результат, а входы, и что корректна она только для чистых функций. Следом обычно спрашивают, чем плох JSON.stringify в роли ключа и как ограничить рост кэша.
Как отвечать
Узнать заранее, что результат не изменится, можно только косвенно: функция чистая, и её аргументы равны прошлым. Дальше три решения: как сравнивать аргументы, где хранить результаты, когда их выбрасывать.
- примитивы: сравниваются по значению.
Object.isотличается от===наNaNи-0, React использует именно его; - объекты по ссылке: самый дешёвый и предсказуемый вариант. Кэш на
WeakMapс объектом в роли ключа не держит память: объект собран сборщиком мусора, запись исчезла. Работает, только если данные иммутабельны; - поверхностное сравнение: поэлементная сверка массива аргументов, так устроены
memoize-oneи массив зависимостейuseMemo; - глубокое сравнение: корректно для мутабельных структур, но стоит O(размер данных) и бывает дороже самого вычисления.
JSON.stringify(args) как ключ годится для простых случаев, но теряет undefined и функции, превращает Map и Set в {}, зависит от порядка ключей и падает на циклических ссылках.
Про объём: безлимитный Map в долгоживущем приложении это утечка. Нужен LRU с ограничением размера, TTL или хранение только последнего вызова. В Redux это делает Reselect: createSelector по умолчанию использует weakMapMemoize, а lruMemoize принимает equalityCheck, maxSize и resultEqualityCheck. Последний отвечает на вопрос буквально: если новый результат равен прошлому, возвращается старая ссылка, и компоненты не перерисовываются.
Где спотыкаются
Мемоизируют нечистые функции: с Date.now(), Math.random() или чтением внешнего состояния. Кэш возвращает устаревшее значение, и баг проявляется не сразу.
Мутируют объект на месте и удивляются, что мемоизация по ссылке не видит изменений. Решение в иммутабельных обновлениях, а не в глубоком сравнении.
Считают, что мемоизация всегда ускоряет. Для дешёвой функции поиск в кэше и сериализация аргументов стоят дороже повторного вызова.