mem模块
作用
缓存函数的运行结果,当参数一样的时候,不再运行,直接读取缓存值
使用
const mem = require('mem');
const m = mem(fn[,options]);
fn:你想运行的函数
options:mem的设置,包括设置缓存时间、缓存key值算法、缓存存储、统计等
具体请参见文档
同步使用
const mem = require('mem');
let i = 0;
function sum() {
++i;
}
const m = mem(sum);
m('h');//i=1
m('h');//i=1
m('m');//i=2
m('m');//i=2
异步使用
const mem = require('mem');
let i = 0;
async function sum() {
++i;
}
const m = mem(sum);
(async function () {
await m();//i=1
await m();//i=1
await m('i');//i=2
})();
源码学习
源码暴露出两个函数:
- mem:主函数
- clear :清除缓存函数
主函数
主函实现:
- 计算key值,将数据存储于Map中
- 数据去重,不存储已经存在过的key值
- 超过设置maxAge时间清除缓存
- 将函数作为key值,存入WeekMap中
1、计算key值
// 默认使用defaultCacheKey函数
const defaultCacheKey = (...args) => {
if (args.length === 0) {
return '__defaultKey';
}
if (args.length === 1) {
const [firstArgument] = args;
if (
firstArgument === null ||
firstArgument === undefined ||
(typeof firstArgument !== 'function' && typeof firstArgument !== 'object')
) {
return firstArgument;
}
}
return JSON.stringify(args);
};
如上是作者计算缓存key的函数,将计算后的key做为cache的键。当然也可以自己传入key的计算函数:
const mem = require('mem');
const m = mem(fn,{
cacheKey:fn //此处可以传入计算key的函数
});
计算key的时候,判断有些不严谨
- 传入值 于 期望返回的不符:当传入NaN、Object类型时,可能会造成与期望不符,例如:
const mem = require('mem');
let i = 0;
function sum() {
++i;
}
const m = mem(sum);
m(NaN);// i=1
m(NaN);// i=1
m({ a: 1 });// i=2
m({ a: 1 });// i=3
// 但是 NaN是不等于NaN,{a:1}也不等于{a:1},而他们的得到值是一样的
- 某些值使用JSON.stringfy()会出现问题:如正则表达式和函数
m(function b() {});// i=1
m(function a() {});// i=1
m(/a/);// i=2
m(/b/);// i=2
2、数据去重
const memoized = function (...args) {
const key = options.cacheKey(...args);
if (cache.has(key)) {
const c = cache.get(key);
return c.data;
}
const ret = fn.call(this, ...args);
setData(key, ret);
if (isPromise(ret) && options.cachePromiseRejection === false) {
// Remove rejected promises from cache unless `cachePromiseRejection` is set to `true`
ret.catch(() => cache.delete(key));
}
return ret;
};
// 作者使用Map的has方法判断去重
// cache(Map类型)在这里充当存储空间,是优于Object的,因为Map可以存储任意类型的key值,而Object只能存储字符串和数字的key值
3、maxAge实现
// mem/index.js文件中
const mapAgeCleaner = require('map-age-cleaner');
...
if (typeof options.maxAge === 'number') {
mapAgeCleaner(options.cache);
}
...
// 使用mapAgeCleaner函数,覆盖了Map.set()方法,调用了cleanup()方法。
在cleanup内部有如下判断超时的机制
...
// map-age-cleaner/index.js文件中
const delay = item[ 1 ][ property ] - Date.now();
if (delay <= 0) { // 此处判断是否超时
// Remove the item immediately if the delay is equal to or below 0
map.delete(item[ 0 ]);// 超时了则删除当前key的数据
processingDeferred.resolve();
return;
}
...
4、缓存函数
...
mimicFn(memoized, fn);
cacheStore.set(memoized, options.cache);
...
// 以函数做为键名,cache作为值,存入cacheStore(weekMap类型)中
// cacheStore是weekMap类型,在此处使用该类型的作用时,weekMap内部的key值是弱引用,当外部引用消失的时候,自动销毁内部对应的键值,防止内存溢出。
清除函数
调用clear方法,手动清楚缓存
module.exports.clear = fn => {
const cache = cacheStore.get(fn);
if (cache && typeof cache.clear === 'function') {
cache.clear();
}
};
结论
优点:
- 快速缓存函数运行结果
不足:
- 在key计算时,不够严谨
- 未设置默认的maxAge时间(当未设置maxAge则为永久缓存),可能会造成内存泄漏