[源碼學習]前端緩存工具fast-memoize和nano-memoize

至今天(2018年9月7日),這2個工具的實現源碼思想是極其類似的,基本上,只要閱讀了其中一個源碼,也就瞭解了另一個的實現。git

fast-memoize導圖:github

初識

大概說說它們的實現思路:數組

  1. 定義緩存結構,其中fast使用了無prototype的對象nano使用了普通對象
  2. 定義序列化方法:當檢測到是單參數時,都是選擇JSON.stringify,而多個參數,二者有不一樣(後面再說)。
  3. 定義策略:也就是緩存的具體方法,其實很簡單,就是對當前緩存結構查找,找到就返回,找不到就從新運行,
    二者都使用了bind方法注入參數,能夠省去運行時再去查找參數。

接着分析二者的異同:緩存

相同處:函數

  • 都使用了JSON.stringify做爲序列化方法,由於它是原生的。
  • 都對返回的緩存函數進行了參數注入(這是一個極大提高性能的方法)。
  • 對單參數仍是多參數的判斷都是使用func.length(形參的數量判斷),由於func.lengtharguments.length這種動態判斷性能會好不少。

不一樣點:工具

  • fast使用了無prototype的對象nano使用了普通對象(這一點性能上相差很少)。
  • 當遇到多個參數時,fast仍是繼續對arguments進行序列化,而nano則複雜一點,它經過用數組將每一次多個參數保存起來,

後續經過遍歷每一個參數進行全等對比===,判斷是否從緩存調取結果。性能

  • 一樣是多個參數,nano增長了一個參數max,可讓用戶自定義須要進行對比參數的長度。

深刻

接着看下第二點不一樣點的源碼:
主要看nano-memoizethis

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

總結

一個表格總結二者最大不一樣,假設:

  • 忽略===的執行時間
  • 使用的參數分爲 引用相同 和 引用不一樣(可是深比較都爲true)
    例如:{x:1}{x:1}
耗時操做 多個參數(引用相同) 多個參數(引用不一樣)
狀態 首次運行 後續運行 首次運行 後續運行
fast 序列化+運行函數 序列化比較 序列化+運行函數 序列化比較
nano 運行函數 0(===比較) 運行函數 運行函數(===比較失敗)

源碼(帶註釋)倉庫

相關文章
相關標籤/搜索