至今天(2018年9月7日)酱鸭,這2個(gè)工具的實(shí)現(xiàn)源碼思想是極其相似的,基本上垛吗,只要閱讀了其中一個(gè)源碼凹髓,也就了解了另外一個(gè)的實(shí)現(xiàn)。
fast-memoize導(dǎo)圖:
初識(shí)
大概說(shuō)說(shuō)它們的實(shí)現(xiàn)思路:
- 定義緩存結(jié)構(gòu)职烧,其中
fast
使用了無(wú)prototype的對(duì)象
扁誓,nano
使用了普通對(duì)象
。 - 定義序列化方法:當(dāng)檢測(cè)到是單參數(shù)時(shí)蚀之,都是選擇JSON.stringify蝗敢,而多個(gè)參數(shù),兩者有不同(后面再說(shuō))足删。
- 定義策略:也就是緩存的具體方法寿谴,其實(shí)很簡(jiǎn)單,就是對(duì)當(dāng)前緩存結(jié)構(gòu)查找失受,找到就返回讶泰,找不到就重新運(yùn)行,
兩者都使用了bind
方法注入?yún)?shù)拂到,可以省去運(yùn)行時(shí)再去查找參數(shù)痪署。
接著分析兩者的異同:
相同處:
- 都使用了
JSON.stringify
作為序列化方法,因?yàn)樗窃摹?/li> - 都對(duì)返回的緩存函數(shù)進(jìn)行了參數(shù)注入(這是一個(gè)極大提升性能的方法)兄旬。
- 對(duì)單參數(shù)還是多參數(shù)的判斷都是使用
func.length
(形參的數(shù)量判斷)狼犯,因?yàn)?code>func.length比arguments.length
這種動(dòng)態(tài)判斷性能會(huì)好很多。
不同點(diǎn):
-
fast
使用了無(wú)prototype的對(duì)象
,nano
使用了普通對(duì)象
(這一點(diǎn)性能上相差不多)悯森。 - 當(dāng)遇到多個(gè)參數(shù)時(shí)宋舷,
fast
還是繼續(xù)對(duì)arguments
進(jìn)行序列化,而nano
則復(fù)雜一點(diǎn)瓢姻,它通過(guò)用數(shù)組將每一次多個(gè)參數(shù)保存起來(lái)祝蝠,
后續(xù)通過(guò)遍歷每個(gè)參數(shù)進(jìn)行全等對(duì)比===
,判斷是否從緩存調(diào)取結(jié)果幻碱。 - 同樣是多個(gè)參數(shù)绎狭,
nano
增加了一個(gè)參數(shù)max
,可以讓用戶自定義需要進(jìn)行對(duì)比參數(shù)的長(zhǎng)度收班。
深入
接著看下第二點(diǎn)不同點(diǎn)的源碼:
主要看nano-memoize
:
function multiple(f,k,v,eq,change,max=0,...args) {
// 用來(lái)儲(chǔ)存i(當(dāng)前對(duì)比的參數(shù)索引)和緩存值
const rslt = {};
// k是一個(gè)專(zhuān)門(mén)存放多個(gè)參數(shù)的數(shù)組 格式類(lèi)似
// [[...args],[...args],[...args]...]
for(let i=0;i<k.length;i++) { // an array of arrays of args
let key = k[i];
// 判斷是否需要使用max
if(max) { key = key.slice(0,max); }
// 當(dāng)前長(zhǎng)度相等或者有max值坟岔,開(kāi)始進(jìn)行對(duì)比
if(key.length===args.length || (max && key.length<args.length)) {
// 獲取長(zhǎng)度
const max = key.length - 1;
for(let j=0;j<=max;j++) {
// 如果發(fā)現(xiàn)不等谒兄,直接跳出
if(!eq(key[j],args[j])) { break; } // go to next key if args don't match
// 當(dāng)?shù)搅俗詈笠豁?xiàng)都沒(méi)跳出 說(shuō)明參數(shù)相同
if(j===max) { // the args matched
// 記錄當(dāng)前索引
rslt.i = i;
// 調(diào)用當(dāng)前參數(shù)的緩存
rslt.v = v[i]; // get the cached value
}
}
}
}
// 如果有i 說(shuō)明是調(diào)用緩存摔桦,如果沒(méi)有i,則添加緩存
const i = rslt.i>=0 ? rslt.i : v.length;
if(change) { change(i); }
// 如果緩存不存在就執(zhí)行func承疲,存在直接返回緩存
return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v;
}
可以看出邻耕,這是通過(guò)2次遍歷,對(duì)[[...args],[...args],[...args]...]
這樣一種結(jié)構(gòu)比較燕鸽,外層遍歷判斷l(xiāng)ength兄世,
length相等才會(huì)進(jìn)入內(nèi)層遍歷,內(nèi)層遍歷就是逐個(gè)判斷了啊研。
// 注入?yún)?shù)御滩,提升性能
f = multiple.bind(
this,
fn,
k,
v,
// 逐個(gè)判斷方式默認(rèn)為 ===
equals || ((a,b) => a===b), // default to just a regular strict comparison
(maxAge ? change.bind(this,v): null), // turn change logging on and bind to arg cache v
maxArgs
);
上面一段則是參數(shù)注入方式和默認(rèn)的對(duì)比方式。
總結(jié)
一個(gè)表格總結(jié)兩者最大不同党远,假設(shè):
- 忽略===的執(zhí)行時(shí)間
- 使用的參數(shù)分為 引用相同 和 引用不同(但是深比較都為true)
例如:{x:1}
和{x:1}
耗時(shí)操作 | 多個(gè)參數(shù)(引用相同) | 多個(gè)參數(shù)(引用不同) | ||
---|---|---|---|---|
狀態(tài) | 首次運(yùn)行 | 后續(xù)運(yùn)行 | 首次運(yùn)行 | 后續(xù)運(yùn)行 |
fast | 序列化+運(yùn)行函數(shù) | 序列化比較 | 序列化+運(yùn)行函數(shù) | 序列化比較 |
nano | 運(yùn)行函數(shù) | 0(===比較) | 運(yùn)行函數(shù) | 運(yùn)行函數(shù)(===比較失敗) |