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則爲永久緩存),可能會造成內存泄漏