Angular源碼解讀 - setupModuleLoader函數

function publishExternalAPI(angular){}
publishExternalAPI就是將一些通用的方法掛載到全局變量angular上,然後我們就可以以API的形式進行調用
函數首先傳入一個angular的全局變量,下面將會遇到在前面定義的好的一個extend函數

function extend(dst) {
    var h = dst.$$hashKey;
    return forEach(arguments, function(obj) {
        obj !== dst && forEach(obj, function(value, key) {
            dst[key] = value
        })
    }), setHashKey(dst, h), dst
}

這裏的方法就是將某些通用的方法掛到一個對象上面,extend傳入和返回都是dst形參對象,裏面經歷了兩個forEach,forEach也是angular前面定義好的函數,extend根據傳入的arguments個數進行判斷,dst是需要掛載的對象,arguments中除了dst外都是需要被掛載到dst中的對象
obj !== dst && forEach(obj, function(value, key) {})
上面這句就是將arguments中的dst對象排除出去,然後對dst進行對象掛載
然後就轉化成

angular['boostrap'] = boostrap;
angular['copy'] = copy;
angular['extend'] = extend;
//...省略若干個通用的方法

上面就不一一列出所有方法,當然當我們對加載了angular.js的頁面進入控制檯進行打印就會很清晰的看出這裏的全部方法
console.log(angular);
如圖所示
[外鏈圖片轉存失敗(img-hOr6YKs6-1567235827024)(https://cloud.githubusercontent.com/assets/17243165/15562024/16e25fd2-232e-11e6-9093-fad75a9fed3d.png)]
這些通用的工具函數在實際的項目中會經常使用到,挺方便的例如forEach,toJson和fromJson這些常用函數
當然就如上面的extend也可以是做API調用,也寫個簡單的例子,同樣在控制器裏面輸入一段代碼,然後打印,這裏創建一個空對象,並把該對象後面的對象拷貝並掛載到該空對象中,並賦值給wscat,那麼wscat對象就有對應的這些屬性
var wscat = angular.extend({}, {name:"wscats",skill:"none"})
console.log(wscat)
[外鏈圖片轉存失敗(img-IDoZoX6h-1567235827026)(https://cloud.githubusercontent.com/assets/17243165/15562172/7bf9a6c2-232f-11e6-9a95-a7fc549c2cb6.png)]

咱們繼續往下看
angularModule = setupModuleLoader(window)
這裏爲angular對象創建和加載module()函數,這裏我在Angular源碼解讀setupModuleLoader函數有詳細的分析,這裏就不多說了,有需要的的可以回頭看一下
繼續下面這句

try {
    angularModule("ngLocale")
} catch (e) {
    angularModule("ngLocale", []).provider("$locale", $LocaleProvider)
}

try代碼塊裏面首先判斷是否獲取到獲取ngLocale模塊,如果我們獲取不到,則就報個異常(ngLocale默認沒有創建的)
catch代碼塊接受上面的異常(也就是獲取不到ngLocale模塊走catch分支),然後在這個位置註冊ngLocale模塊

簡單的說這裏angular.module在創建模塊的時候,傳遞一個參數的時候,是獲取模塊;傳遞一個以上的是創建新模塊;所以try裏面是獲取,catch裏面就是創建了
那麼這裏就成功的創建了一個ngLocale模塊,其實angularModule實際上就是angular.module
angularModule = angular.module
angularModule("ngLocale", []) = angular.module("ngLocale", [])
所以我們就可以鏈式去調用對象moduleInstance中的directive和provider

annotate用來分析一個函數的簽名,也就是函數的參數

function annotate(fn) {
    var $inject, fnText, argDecl, last;
    return "function" == typeof fn ? ($inject = fn.$inject) || ($inject = [], fn.length && (fnText = fn.toString().replace(STRIP_COMMENTS, ""), argDecl = fnText.match(FN_ARGS), forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
            $inject.push(name)
        })
    })), fn.$inject = $inject) : isArray(fn) ? (last = fn.length - 1, assertArgFn(fn[last], "fn"), $inject = fn.slice(0, last)) : assertArgFn(fn, "fn", !0), $inject
}

注入器$injector的annotate方法的作用爲分析函數的參數簽名
上面代碼把它展開如下

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;
  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
          arg.replace(FN_ARG, function(all, underscore, name){
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

annotate傳參的第一種情況(傳函數)
首先判斷傳入的值是否函數
typeof fn == 'function'
再判斷函數有沒有inject!(inject屬性,如果沒有的話 `!(inject = fn.$inject)首先經過toString()序列化 處理轉換爲字符串,fnText = fn.toString().replace(STRIP_COMMENTS, ‘’);`

arg.replace(FN_ARG, function(all, underscore, name){
    $inject.push(name);
});

STRIP_COMMENTS和FN_ARG在前面定義好的正則表達式
STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm
FN_ARG = /^\s*(_?)(\S+?)\1\s*$/
以上正則獲取了依賴模塊的名稱並存入$inject數組中返回

簡單演示這部分函數,走了了個類似的流程代碼方便理解,如下

var
    FN_ARG_SPLIT = /,/,
    FN_ARG = /^\s*(_?)(\S+?)\1\s*$/,
    FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m,
    STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;

function annotate(fn) {
    var $inject,
        fnText,
        argDecl;
    if (typeof fn == 'function') {
        if (!($inject = fn.$inject)) {
            $inject = [];
            if (fn.length) {
                fnText = fn.toString().replace(STRIP_COMMENTS, '');
                argDecl = fnText.match(FN_ARGS);
                argDeclSplit = argDecl[1].split(FN_ARG_SPLIT);
                argDeclSplit[0].replace(FN_ARG, function(all, underscore, name) {
                    $inject.push(name);
                });
                argDeclSplit[1].replace(FN_ARG, function(all, underscore, name) {
                    $inject.push(name);
                });
                argDeclSplit[2].replace(FN_ARG, function(all, underscore, name) {
                    $inject.push(name);
                });
                console.log(fn)
                console.log(fnText);
                console.log(argDecl);
                console.log(argDeclSplit);
                console.log($inject);
            }
            fn.$inject = $inject;
        }
    }
    return $inject;
}
annotate(function(a , $scope ,$window) {});

打印的結果如下圖
image
裏面省略了forEach等angular方法,不過思路和目的是相同的,可以看到一些諸如空格等被正則正確的處理掉,然後最後返回一個數組
annotate傳參的第二種情況(傳數組)
那就是走了else if (isArray(fn)) {}的分支
這裏用了angular自定義的函數assertArgFn

function assertArg(arg, name, reason) {
    if (!arg) throw ngMinErr("areq", "Argument '{0}' is {1}", name || "?", reason || "required");
    return arg
}

function assertArgFn(arg, name, acceptArrayAnnotation) {
    return acceptArrayAnnotation && isArray(arg) && (arg = arg[arg.length - 1]), assertArg(isFunction(arg), name, "not a function, got " + (arg && "object" == typeof arg ? arg.constructor.name || "Object" : typeof arg)), arg
}

其實就是處理數組中最後的元素,而最後的元素是個函數

var fn = ["a", "$scope", "$window", function(){}];
function annotate(fn){
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
    console.log($inject)
}
function assertArgFn(arg, name, acceptArrayAnnotation) {
    return acceptArrayAnnotation && (arg = arg[arg.length - 1]), arg
}
annotate(fn);

image
createInternalInjector返回一個對象如下

return {
    invoke: invoke,
    instantiate: instantiate,
    get: getService,
    annotate: annotate,
    has: function(name) {
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name)
    }
}

裏面有五個方法invoke,instantiate,get,annotate,has
annotate已經詳細介紹過了,就是獲取函數的參數
instantiate則是用來實例化一個對象
get用來獲得一個服務的實例對象
invoke用來調用一個函數

首先分析invoke函數

function invoke(fn, self, locals) {
    var length, i, key, args = [],
        $inject = annotate(fn);
    for (i = 0, length = $inject.length; length > i; i++) {
        if (key = $inject[i], "string" != typeof key) throw $injectorMinErr("itkn", "Incorrect injection token! Expected service name as string, got {0}", key);
        args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key))
    }
    switch (fn.$inject || (fn = fn[length]), self ? -1 : args.length) {
        case 0:
            return fn();
        case 1:
            return fn(args[0]);
        case 2:
            return fn(args[0], args[1]);
        case 3:
            return fn(args[0], args[1], args[2]);
        case 4:
            return fn(args[0], args[1], args[2], args[3]);
        case 5:
            return fn(args[0], args[1], args[2], args[3], args[4]);
        case 6:
            return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
        case 7:
            return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
        case 8:
            return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
        case 9:
            return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
        case 10:
            return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
        default:
            return fn.apply(self, args)
    }
}

首先把函數的fn的$inject = annotate(fn);參數用annotate以數組形式保存在$inject裏面
再驗證函數的參數

for (i = 0, length = $inject.length; length > i; i++) {
    if (key = $inject[i], "string" != typeof key) throw $injectorMinErr("itkn", "Incorrect injection token! Expected service name as string, got {0}", key);
    args.push(locals && locals.hasOwnProperty(key) ? locals[key] : getService(key))
}

不正確註冊簽名,服務名稱應該爲字符串Incorrect injection token! Expected service name as string
然後判斷是否有傳入locals,如果有則判斷它的屬性是否含有fn函數的參數,如果有並且包含這些參數然後把他們保存到args數組裏面

例如angular在bootstrap方法中調用invoke函數

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
    function(scope, element, compile, injector, animate) {
        scope.$apply(function() {
            element.data('$injector', injector);
            compile(element)(scope);
        });
    }]
);
return injector;

開始args.push(getService(rootScope)),args.push(getService(rootScope)),args.push(getService(rootElement))…
然後判斷fn.inject(fn=fn[length])fnselfself?1:args.lengthself1defaultreturnfn.apply(self,args)selfinject || (fn = fn[length]),如果是數組的話就會拿數組最後一個,也就是傳入最後的那個函數賦給fn 然後進入因爲沒有傳入self所以`self ? -1 : args.length`self就是-1,就是走default分支`return fn.apply(self, args)`,如果self有值得話其實很好理解就是把`inject = annotate(fn),key = inject[i]annotatefunction()injector.invoke([inject[i]`annotate函數拿到的參數一個一個的傳進去function(){}裏面,然後後面的injector.invoke(['rootScope’, ‘rootElement,rootElement', 'compile’, ‘injector,injector', 'animate’,function(scope, element, compile, injector, animate) {})’])的回調函數就可以在執行的時候調用裏面的依賴
我寫了個簡單的流程來模仿invoke函數實際上做了什麼如下

var fn = ['wsccat','wscats',function fn(){
    console.log(arguments);
}];
    function invoke(fn, self, locals) {
var args = ['wsccat','wscats'];//annotate(fn)處理後放入args
    var fn = fn[fn.length-1];
var self = self ? -1 : fn.length-1;//switch處理
    switch (self) {
        case 0:
        return fn();
            break;
        default:
        return fn.apply(self, args);
    }
    }
//invoke(fn);
    invoke(['wsccat','wscats',function fn(){
    console.log(arguments);
}]);

最後執行的打印結果是運行了閉包函數並輸出apply後'wsccat','wscats'的兩個參數
image
實際上apply會執行匿名函數一次,此時的匿名函數fn(){}已經有了wsccat,wscats兩個參數了

get就是createInternalInjector裏面的getService閉包函數
後面是這樣調用的var provider = providerInjector.get(servicename + providerSuffix);
也就是相當於var provider = providerInjector.get(servicename + ‘Provider’);

function getService(serviceName) {
    if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) throw $injectorMinErr("cdep", "Circular dependency found: {0}", path.join(" <- "));
        return cache[serviceName]
    }
    try {
        return path.unshift(serviceName), cache[serviceName] = INSTANTIATING, cache[serviceName] = factory(serviceName)
    } finally {
        path.shift()
    }
}

先看看在執行getService時候,createInternalInjector函數體裏有個path = [],path的數組
首先執行這個函數前要用到createInternalInjector函數傳入的參數createInternalInjector(cache, factory),cache和factory,比如下面的這句

createInternalInjector(providerCache, function() {
    throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
}),

providerCache對象是下面這個

providerCache = {
    $provide: {
        provider: supportObject(provider),
        factory: supportObject(factory),
        service: supportObject(service),
        value: supportObject(value),
        constant: supportObject(constant),
        decorator: decorator
    }
}

這些參數再放到閉包函數getService裏面執行

createInjector方法裏面,其實是通過createInternalInjector方法來創建注入器的。

var INSTANTIATING = {}, providerSuffix = "Provider",
path = [],
loadedModules = new HashMap,
providerCache = {
    $provide: {
        provider: supportObject(provider),
        factory: supportObject(factory),
        service: supportObject(service),
        value: supportObject(value),
        constant: supportObject(constant),
        decorator: decorator
    }
}, providerInjector = providerCache.$injector = createInternalInjector(providerCache, function() {
    throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
}),
instanceCache = {}, instanceInjector = instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) {
    var provider = providerInjector.get(servicename + providerSuffix);
    return instanceInjector.invoke(provider.$get, provider)
});

這裏會用createInternalInjector方法創建兩個$injector對象
providerInjector是用來創建provider的,instanceInjector是用來創建一個對象實例的。
第一個創建providerInjector對象

providerInjector = providerCache.$injector = createInternalInjector(providerCache, function() {
    throw $injectorMinErr("unpr", "Unknown provider: {0}", path.join(" <- "))
})

實際上providerCache就變成了這樣

providerCache = {
    $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
    },
    $injector:{
          get:getService,
          annotate:annotate,
          instantiate:instantiate,
          invoke:invoke,
          has:has
    }
}

而providerInjector變成了這樣

providerInjector:{
      nvoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}

第二個創建instanceInjector對象

instanceInjector = instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) {
    var provider = providerInjector.get(servicename + providerSuffix);
    return instanceInjector.invoke(provider.$get, provider)
})

實際上instanceCache就變成了這樣

instanceCache:{
      $injector:{
          invoke: invoke,
          instantiate: instantiate,
          get: getService,
          annotate: annotate,
          has: has
      }
}

而instanceInjector變成了這樣

instanceInjector = {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}

從angular源碼可以知道,創建provider的這幾種方式:provider/factory/service/constant/value,左往右靈活性越低,provider方法是基礎,其他都是調用provider方法實現的,只是的參數不一樣而已

function provider(name, provider_) {
       assertNotHasOwnProperty(name, 'service');
       if (isFunction(provider_) || isArray(provider_)) {
        provider_ = providerInjector.instantiate(provider_);
       }
       if (!provider_.$get) {
         throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
       }
       return providerCache[name + providerSuffix] = provider_;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章