angular原始碼剖析之Provider系列--CacheFactoryProvider

john23.net發表於2019-05-22

CacheFactoryProvider 簡介

原始碼裡是這麼描述的:
Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
them.
意思就是通過cacheFactory可以構造一個Cache物件來給予訪問和執行許可權。

這個Cache物件官方文件是這麼說的:
A cache object used to store and retrieve data, primarily used by $http and the script directive to cache templates and other data.
用我自己的話來說就是 提供儲存和訪問快取物件的服務,angular內部主要被$http,script指令用於
快取template和其他資料。我們自己可以在Controller內部使用。

CacheFactoryProvider 用法

<!doctype html>
  <html lang="en" ng-app="myapp">
   <head>
    <meta charset="UTF-8">
    <meta name="Generator" content="EditPlus®">
    <meta name="Author" content="">
    <meta name="Keywords" content="">
    <meta name="Description" content="">
    <title>Document</title>
    <script src="js/angular.js"></script>
   </head>
   <body>
    <div ng-controller="MyController">
    </div>
     <script >
       var app=angular.module('myapp',[]);
       app.controller('MyController',function($scope,$cacheFactory){
         var myCache = $cacheFactory("myCache",{capacity: 6});
         //var myCache1 = $cacheFactory("myCache",{capacity: 6}); //會報錯
         myCache.put("name","john");
         myCache.put("name1","wonder");
         myCache.put("name","john");
      });

       app.controller('getCacheController',['$scope','$cacheFactory',
          function($scope,$cacheFactory){  
                var cache = $cacheFactory.get('myCache');  
                var name = cache.get('name');  
                console.log(name);  //列印john
            }]);  
     </script>
   </body>
  </html>

看了上面這個一個簡單的例子,讀者可能會產生如下疑惑:

  1. 不是名為CacheFactoryProvider嗎,怎麼在程式碼裡只看到cacheFactory呢?
  2. cacheFactory是怎麼儲存物件的?
    下面我們來依次解答這兩個問題

$cacheFactory的注入

我們首先來看第一個問題,這個問題要牽涉到angular裡面的依賴注入機制,我們前面的分析也講過,
angular會在啟動之前通過呼叫publishExternalAPI 函式先發布一些擴充套件API,同時定義ng
模組,在定義ng模組的時候就傳入了注入provider的方法

angularModule('ng', ['ngLocale'], ['$provide',
     //通過引數注入$provide
     function ngModule($provide) {
          ///部分程式碼省略
         $provide.provider({
           $cacheFactory: $CacheFactoryProvider,
         });
    }])

$cacheFactory出現了,它是通過javascript的鍵值物件作為鍵傳給provider方法。那麼它是如何儲存
物件的呢?首先我們看它的定義:

CacheFactoryProvider的定義

內部定義了依賴注入核心的$get方法,$get方法返回cacheFactory方法(也就是上面例項程式碼裡的
$cacheFactory引數)。

  function $CacheFactoryProvider() {
    //定義$get方法供依賴呼叫
    //controller中獲取cacheFactory時會呼叫此方法
    //這個$get方法也是獲取provider的關鍵方法
     this.$get = function() {
       var caches = {};//閉包的一個運用

       function cacheFactory(cacheId, options) {
         //部分程式碼省略
         //可以用if來判斷
         if (cacheId in caches) {//如果caches中已經存在cacheId
             //例項程式碼裡丟擲的錯誤就在此處、
             //統一呼叫minErr函式
            throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!"
                                          , cacheId);
          }

           var size = 0,
           //把options 和{id:cacheId} 放入{} 中 不是深拷貝
           stats = extend({}, options, {id: cacheId}),
           data = createMap(),//通過Object.create(null) 建立個空物件
           capacity = (options && options.capacity) || Number.MAX_VALUE,
           lruHash = createMap(),
           freshEnd = null,
           staleEnd = null;
           //返回caches中的一個物件
           return caches[cacheId] = {
                //省略部分程式碼
                //儲存裡講解
                put:function(key,value){
                },
                get: function(key) {
                },
                remove: function(key) {
                },
                removeAll: function() {
                },
                destroy: function() {
                },
                info: function() {
                }
           }
           //重新整理節點次序
           function refresh(entry) {
           }
           //
           function link(nextEntry, prevEntry) {

           }
         }

         //所有的快取
        cacheFactory.info = function() {
          var info = {};
          forEach(caches, function(cache, cacheId) {
            info[cacheId] = cache.info();
          });
          return info;
        };

        cacheFactory.get = function(cacheId) {
            return caches[cacheId];
        };

        return cacheFactory;
     }
  }

CacheFactoryProvider的儲存

儲存分為這幾個核心方法:put,refresh,remove,link

put函式

value會放入data物件中,key會放入lruHash連結串列

      put: function(key, value) {
         if (isUndefined(value)) return;
           //如果設定的capcity小於maxvalue
         if (capacity < Number.MAX_VALUE) {
           //lruHash 存了當前的key 還有可能是 p 和n  (previous和next)
           var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
           //重新整理各節點的次序
           refresh(lruEntry);//把當前entry放入連結串列末尾
         }
         //如果key 在data裡不存在 那麼增加size
         if (!(key in data)) size++;
         data[key] = value;
         //當大於capacity時 會清除最早加入的那個
         if (size > capacity) {
           this.remove(staleEnd.key);//移除淘汰節點stableEnd
         }
         return value;
     }

get函式

Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object
獲取儲存在cache物件中的指定資料

        get: function(key) {
          if (capacity < Number.MAX_VALUE) {
            var lruEntry = lruHash[key];
            if (!lruEntry) return;
            // 獲取first的時候 因為staleEnd為first 所以會讓staleEnd指向 second
            // 內部會執行link 使得 second.p = null
            // first.p =  third  third.n = first
            //stableEnd為 second freshEnd為first
            refresh(lruEntry);
          }
          return data[key];
        }

remove函式

Removes an entry from the {@link $cacheFactory.Cache Cache} object.
從cache物件刪除一個entry

remove: function(key) {
          //如果capacity小於maxvalue
          if (capacity < Number.MAX_VALUE) {
            //先取出當前key的entry
            var lruEntry = lruHash[key];
            if (!lruEntry) return;
            //第一次超過時 freshEnd 為third  lryEntry為first
            if (lruEntry == freshEnd) freshEnd = lruEntry.p;
             //第一次超過時 staleEnd 為first  lryEntry為first
             //所以 會讓 stalEnd 指向second 以便於下次移除時
            if (lruEntry == staleEnd) staleEnd = lruEntry.n;
            //把淘汰節點的一個節點選中
            //第一次超過時 lryEntry.n為 second  lryEntry.p 為null
            //執行結果為 second.p = null
            link(lruEntry.n,lruEntry.p);
            //把當前key從lruHash中刪除
            delete lruHash[key];
          }
          if (!(key in data)) return;
          delete data[key];
          size--;
        }

refresh函式

makes the entry the freshEnd of the LRU linked list。
把entry 放入連結串列的末尾

    function refresh(entry) {
      if (entry != freshEnd) {
        if (!staleEnd) { //staleEnd為空那麼就讓他指向當前entry
          staleEnd = entry;
        } else if (staleEnd == entry) {
          //如果淘汰節點等於當前節點
          staleEnd = entry.n; //用於把 當前的下一個節點 用作淘汰節點
        }
        //放入第一個元素時 entry.n,entry.p都為undefined
        link(entry.n, entry.p); //當前的上一個節點 和當前的下一個節點
        link(entry, freshEnd); // 當前的節點 和 最新的末尾節點
        freshEnd = entry;  
        freshEnd.n = null;
        //第一次執行完 結果為: freshEnd = first  staleEnd為first  
        //first.p=null first.n=null
        //第二次執行完 結果為:freshEnd = second staleEnd為first
        // first.p=null first.n= second
        // scecond.p = first scecond.n = null
        //第三次執行完 freshEnd = third staleEnd為first first.p=null
        //first.n= second
        // second.p = first second.n = null
        // third.p = second third.n = null
      }
    }

link函式

bidirectionally(雙向連結串列) links two entries of the LRU linked list
雙向連結連結串列裡的兩個元素。

function link(nextEntry, prevEntry) {
      //undefined 不等於undefined
      if (nextEntry != prevEntry) {
        //
        if (nextEntry) nextEntry.p = prevEntry;
        //p stands for previous, 'prev' didn't minify
        if (prevEntry) prevEntry.n = nextEntry;
         //n stands for next, 'next' didn't minify
      }
    }

angular原始碼剖析之Provider系列--CacheFactoryProvider

歡迎關注我的公眾號,獲取最新原始碼解析文章!

參考資料:
  1. 【演算法】—— LRU演算法
  2. 快取淘汰演算法--LRU演算法
  3. 漫畫:什麼是LRU演算法?

相關文章