至今天(2018年9月7日),這2個工具的實現源碼思想是極其類似的,基本上,只要閱讀了其中一個源碼,也就瞭解了另一個的實現。git
fast-memoize導圖:github
大概說說它們的實現思路:數組
fast
使用了無prototype的對象
,nano
使用了普通對象
。bind
方法注入參數,能夠省去運行時再去查找參數。接着分析二者的異同:緩存
相同處:函數
JSON.stringify
做爲序列化方法,由於它是原生的。func.length
(形參的數量判斷),由於func.length
比arguments.length
這種動態判斷性能會好不少。不一樣點:工具
fast
使用了無prototype的對象
,nano
使用了普通對象
(這一點性能上相差很少)。fast
仍是繼續對arguments
進行序列化,而nano
則複雜一點,它經過用數組將每一次多個參數保存起來,後續經過遍歷每一個參數進行全等對比===
,判斷是否從緩存調取結果。性能
nano
增長了一個參數max
,可讓用戶自定義須要進行對比參數的長度。接着看下第二點不一樣點的源碼:
主要看nano-memoize
:this
function multiple(f,k,v,eq,change,max=0,...args) { // 用來儲存i(當前對比的參數索引)和緩存值 const rslt = {}; // k是一個專門存放多個參數的數組 格式相似 // [[...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); } // 當前長度相等或者有max值,開始進行對比 if(key.length===args.length || (max && key.length<args.length)) { // 獲取長度 const max = key.length - 1; for(let j=0;j<=max;j++) { // 若是發現不等,直接跳出 if(!eq(key[j],args[j])) { break; } // go to next key if args don't match // 當到了最後一項都沒跳出 說明參數相同 if(j===max) { // the args matched // 記錄當前索引 rslt.i = i; // 調用當前參數的緩存 rslt.v = v[i]; // get the cached value } } } } // 若是有i 說明是調用緩存,若是沒有i,則添加緩存 const i = rslt.i>=0 ? rslt.i : v.length; if(change) { change(i); } // 若是緩存不存在就執行func,存在直接返回緩存 return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v; }
能夠看出,這是經過2次遍歷,對 [[...args],[...args],[...args]...]
這樣一種結構比較,外層遍歷判斷length,
length相等纔會進入內層遍歷,內層遍歷就是逐個判斷了。spa
// 注入參數,提高性能 f = multiple.bind( this, fn, k, v, // 逐個判斷方式默認爲 === 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 );
上面一段則是參數注入方式和默認的對比方式。prototype
一個表格總結二者最大不一樣,假設:
{x:1}
和{x:1}
耗時操做 | 多個參數(引用相同) | 多個參數(引用不一樣) | ||
---|---|---|---|---|
狀態 | 首次運行 | 後續運行 | 首次運行 | 後續運行 |
fast | 序列化+運行函數 | 序列化比較 | 序列化+運行函數 | 序列化比較 |
nano | 運行函數 | 0(===比較) | 運行函數 | 運行函數(===比較失敗) |