Читать один модуль npm в день (2) - mem

Node.js внешний интерфейс регулярное выражение NPM

Серия статей:

  1. Читать один модуль npm в день (1) — имя пользователя

читал вчера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);
};

Как видно из кода выше:

  1. Когда есть только один параметр, и параметр имеет значение null | undefined или тип не является function | object, хеш-функция возвращает параметр напрямую.
  2. Если это не так, возвращаемый параметр передается через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()Преобразуйте его в строку и обработайте! Я был ошеломлен, когда увидел это - раньше я слышал, как люди делают это только в шутку, но я не ожидал, что это произойдет.

Этот метод очень прост и очень читабелен, но есть проблемы:

  1. Когда структура объекта сложная,JSON.stringify()Будет потреблять много времени.

  2. Для различных регулярных объектов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;
}

Общая логика очень понятна, в основном для выполнения двух действий:

  1. будет типаMapизretCacheВ качестве кеша результата выполнения функции ключевое значение кеша равноdefaultCacheKeyхешированный результат.
  2. будет типа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,блог ЭлвинаДобро пожаловать в гости ^_^