Серия статей:
читал вчераusernameПосле исходников версии 3.0.0 я предложил автору Sindre Sorhus по своим собственным идеям.Pull Request, я не ожидал, что Sindre сегодня принял PR и отказался от поддержки Node 4 и обновился до версии 4.0.0, но код ядра не сильно изменился 😊
введение в одно предложение
Модуль npm для чтения сегодня:mem, что сокращает фактическое время выполнения функции за счет кэширования возвращаемого значения функции, тем самым повышая производительность.Текущая версия – 3.0.1, а еженедельный объем загрузки – около 3,5 млн.
Применение
const mem = require('mem');
// 同步函数缓存
let i = 0;
const counter = () => ++i;
const memoized = mem(counter);
memoized('foo');
//=> 1
memoized('foo');
//=> 1 参数相同,返回换成的结果 1
memoized('bar');
//=> 2 参数变化,counter 函数再次执行,返回 2
memoized('bar');
//=> 2
// 异步函数缓存
let j = 0;
const asyncCounter = () => Promise.resolve(++j);
const asyncmemoized = mem(asyncCounter);
asyncmemoized().then(a => {
console.log(a);
//=> 1
asyncmemoized().then(b => {
console.log(b);
//=> 1
});
});
Вышеупомянутое использованиеmemКроме того, он также поддерживает такие функции, как установка времени кеша, настройка значения хэша кеша и статистика попаданий в кеш.
Изучение исходного кода
хэш-функция
для того, чтобы бытьmem
Обработанная функция может возвращать одно и то же значение для одного и того же параметра, тогда параметр должен быть хеширован, а затем результат хеширования будет использоваться какkey
, результат функции такойvalue
Кэшировать его, возьмем простейший пример:
const cache = {};
// 缓存 arg1 的运行结果
const key1 = getHash(arg1);
cache[key1] = func(arg1);
// 缓存 arg2 的运行结果
const key2 = getHash(arg2);
cache[key2] = func(arg2);
Ключ в том, чтоgetHash
Эта хэш-функция: как обрабатывать разные типы данных? Как обрабатывать сравнения между объектами? На самом деле, это тоже вопрос, который часто задают в интервью: как проводить глубокие сравнения? Посмотрим, как это написано в исходном коде:
// 源代码 2-1: mem 的哈希函数
const defaultCacheKey = (...args) => {
if (args.length === 1) {
const [firstArgument] = args;
if (
firstArgument === null ||
firstArgument === undefined ||
(typeof firstArgument !== 'function' && typeof firstArgument !== 'object')
) {
return firstArgument;
}
}
return JSON.stringify(args);
};
Как видно из кода выше:
- Когда есть только один параметр, и параметр имеет значение null | undefined или тип не является function | object, хеш-функция возвращает параметр напрямую.
- Если это не так, возвращаемый параметр передается через
JSON.stringify()
ценность .
Прежде всего, вы можете просмотреть типы данных, определенные в ES6, включая 6 примитивных типов (Boolean | Nunber | Null | Undefined | String | Symbol) и типы Object. Хеш-функция в исходном коде должна различать разные типы, потому что прямое сравнение типа Object не соответствует эффекту, которого мы должны достичь здесь:
const object1 = {a: 1};
const object2 = {a: 1};
console.log(object1 === object2);
// => flase
// 期望效果
console.log(defaultCacheKey(object1) === defaultCacheKey(object2));
// => true
Сначала я подумал, что автор будет делать специальную обработку, оценивая разные типы данных (аналогичноРеализация Lodash _.isEqual()), я не ожидал, что метод будет таким жестоким: напрямую передавать данные типа Object черезJSON.stringify()
Преобразуйте его в строку и обработайте! Я был ошеломлен, когда увидел это - раньше я слышал, как люди делают это только в шутку, но я не ожидал, что это произойдет.
Этот метод очень прост и очень читабелен, но есть проблемы:
-
Когда структура объекта сложная,
JSON.stringify()
Будет потреблять много времени. -
Для различных регулярных объектов
JSON.stringify()
Результаты{}
, что не соответствует ожидаемому эффекту хэш-функции.console.log(JSON.stringify(/Sindre Sorhus/)); // => '{}' console.log(JSON.stringify(/Elvin Peng/)); // => '{}'
Первый вопрос в порядке, потому что если он пройдетJSON.stringify()
При хешировании, если есть проблема с производительностью,mem
Поддерживает передачу пользовательской хеш-функции, которую можно решить, написав эффективную хеш-функцию самостоятельно.
Вторая проблема заключается в том, что функция не оправдывает ожиданий и требует исправления.
структура хранения
Без учета дополнительных параметров исходный код поддержки синхронных функций можно упростить следующим образом:
// 源代码 2-2 mem 核心逻辑
const mimicFn = require('mimic-fn');
const cacheStore = new WeakMap();
module.exports = (fn) => {
const memoized = function (...args) {
const cache = cacheStore.get(memoized);
const key = defaultCacheKey(...args);
if (cache.has(key)) {
const c = cache.get(key);
return c.data;
}
const ret = fn.call(this, ...args);
const setData = (key, data) => {
cache.set(key, {
data,
});
};
setData(key, ret);
return ret;
}
const retCache = new Map();
mimicFn(memoized, fn);
cacheStore.set(memoized, retCache);
return memoized;
}
Общая логика очень понятна, в основном для выполнения двух действий:
- будет типа
Map
изretCache
В качестве кеша результата выполнения функции ключевое значение кеша равноdefaultCacheKey
хешированный результат. - будет типа
WeakMap
изcacheStore
В целом кеш, ключевым значением кеша является сама функция.
Кэш второго уровня, образованный двумя вышеуказанными действиями, реализует основную функцию модуля, и два типа выбора здесь очень достойны изучения.
retCache
выберитеMap
введите вместоObject
типа в основном потому чтоMap
Ключ-значение поддерживает все типы, аObject
Значение ключа поддерживает только строки, кроме того, это предпочтительный выбор для структуры данных кеша.Map
Типы также имеют следующие преимущества:
-
Map.size
Свойство может легко получить текущее количество кешей -
Map
Тип поддержкиclear()
|forEach()
и другие часто используемые функции инструмента -
Map
Типы итерабельны по умолчанию, т.е. поддержкаiterable protocol
cacheStore
выберитеWeakMap
введите вместоMap
Тип в основном потому, что он имеет то преимущество, что не увеличивает количество ссылок, что больше способствует сборке мусора движком Node.js.
Асинхронная поддержка
Я изначально планировал написать часть про асинхронную поддержку, но уже 1:00 ночи, давайте просто подумаем, ложитесь спать пораньше 😪
Заинтересованные друзья могут читать сами~
напиши в конце
В дополнение к одной ошибке, упомянутой выше,mem
Также существует вероятность утечек памяти: когда срок действия кэшированных данных истек (то есть время кэширования больше установленного maxAge), они не будут автоматически очищаться, что может привести к сбою памяти, занятой недопустимым кэшем, при слишком много кэшированных данных. освобождается вовремя, что приводит к утечке памяти. Подробнее см.Issue #14: Memory leak: old results are not deleted from the cache.
Намеренно исключено из интерпретации исходного кода 2-2.mimicFn(memoized, fn);
роль, почему? Потому что я буду читать завтраmimicFnЯ надеюсь, что вы продолжите поддерживать этот модуль.
О себе: Окончил Хуаке, работаю в Tencent,блог ЭлвинаДобро пожаловать в гости ^_^