angular源碼剖析之Provider系列--CacheFactoryProvider

CacheFactoryProvider 簡介

源碼裏是這麼描述的:
Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
them.
意思就是經過cacheFactory能夠構造一個Cache對象來給予訪問和執行權限。javascript

這個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內部使用。html

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>

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

  1. 不是名爲CacheFactoryProvider嗎,怎麼在代碼裏只看到cacheFactory呢?
  2. cacheFactory是怎麼存儲對象的?
    下面咱們來依次解答這兩個問題

$cacheFactory的注入

咱們首先來看第一個問題,這個問題要牽涉到angular裏面的依賴注入機制,咱們前面的分析也講過,
angular會在啓動以前經過調用publishExternalAPI 函數先發布一些擴展API,同時定義ng
模塊,在定義ng模塊的時候就傳入了注入provider的方法angularjs

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

$cacheFactory出現了,它是經過javascript的鍵值對象做爲鍵傳給provider方法。那麼它是如何存儲
對象的呢?首先咱們看它的定義:算法

CacheFactoryProvider的定義

內部定義了依賴注入核心的$get方法,$get方法返回cacheFactory方法(也就是上面實例代碼裏的
$cacheFactory參數)。api

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對象中的指定數據app

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對象刪除一個entryide

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
      }
    }

歡迎關注個人公衆號,獲取最新源碼解析文章!

參考資料:
  1. 【算法】—— LRU算法
  2. 緩存淘汰算法--LRU算法
  3. 漫畫:什麼是LRU算法?
相關文章
相關標籤/搜索