昨天晚上寫完angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了,給今天定了一個題angular源碼分析:injector.js文件,以及angular的加載流程,但是想了想,加載流程還是放到后面統一再講比較好。
如果你沒有看過筆者的angular源碼分析:angular中的依賴注入式如何實現的,可以點擊看看,在其中講過的內容,我將不會再這里重復,這一期將作那一期的補充。
一、從createInjector函數開始
先省去具體實現,總體看看:函數擁有兩個參數,modulesToLoad, strictDi,從單詞命名上來看,第一個參數是要沒加載的模塊,第二參數是嚴格的依賴注入;另外函數對象本身綁定了一個annotate,在之前我們講過annotate是一個可以將函數中的參數提出來的函數。
function createInjector(modulesToLoad, strictDi) { ...}
createInjector.$$annotate = annotate;
二、理解createInjector函數的實現
先上代碼:
strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = { //用存放provider的cache
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) { //調用createInternalInjector生成內部的注入器
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {}, //用來存放服務實例的cache
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(serviceName, caller) {
var provider = providerInjector.get(serviceName + providerSuffix, caller);
return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
return instanceInjector;
1.這個函數做了什么。
基本上是定義一堆東西,其中最重要的是providerCache和providerInjector 以及instanceCache和instanceInjector。關於createInternalInjector這個函數,在angular源碼分析:angular中的依賴注入式如何實現的中講過,主要功能是利用提供的cache(第一個參數u)和factory(第二參數),構造一個內部注入器,其本身也是工廠函數。
2.providerCache 和$provide
我們可以這樣理解,providerCache中存放的就是各種服務的提供者的實例。比如定義一個服務,叫"dapeng",那么提供者就是"dapengProvider"。而這個容器(providerCache)在初始化時,就默認放入了一個$provide。
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
很眼熟吧,這是不是可以用於定義服務的幾種方式呢?
3.forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
中的loadModules函數:
////////////////////////////////////
// Module Loading
////////////////////////////////////
function loadModules(modulesToLoad) {
assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
var runBlocks = [], moduleFn;
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
function runInvokeQueue(queue) { //執行調用隊列
var i, ii;
for (i = 0, ii = queue.length; i < ii; i++) {
var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}
try {
if (isString(module)) {
moduleFn = angularModule(module); //加載模塊
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); //遞歸加載依賴模塊,獲取所有模塊的run函數定義的代碼。
runInvokeQueue(moduleFn._invokeQueue); //moduleFn._invokeQueue是什么鬼,先留坑在此
runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
}
} catch (e) {
if (isArray(module)) {
module = module[module.length - 1];
}
if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
// Safari & FF's stack traces don't contain error.message content
// unlike those of Chrome and IE
// So if stack doesn't contain message, we create a new string that contains both.
// Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
/* jshint -W022 */
e = e.message + '\n' + e.stack;
}
throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
module, e.stack || e.message || e);
}
});
return runBlocks;
}
angularModule是什么鬼,留坑先不講。可以先理解為,用於加載一個module。
runBlocks將得到一個數組,這個數組的元素是一些函數,這些函數是定義模塊后通過run(function(){})
注冊的函數。
runInvokeQueue函數,將執行調用隊列,可以從這個函數的實現上來看,參數queue應該是一個二維數組。[['name','index',params]]
,這個函數將循環處理queue數組。
** moduleFn._invokeQueue 和 moduleFn._configBlocks** 本期先不講,留坑在此,等講“加載流程”再講。
那么,這個函數最紅就是返回的一個函數數組:runBlocks。
可以基本推出:forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });
是執行所有的模塊中run的代碼,而在run的代碼執行前,先執行了服務的定義代碼和模塊config代碼。
三、$provider
還是先上代碼:
////////////////////////////////////
// $provider
////////////////////////////////////
function supportObject(delegate) {
return function(key, value) {
if (isObject(key)) { //如果key是一個對象
forEach(key, reverseParams(delegate));
} else {
return delegate(key, value);
}
};
}
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_;
}
function enforceReturnValue(name, factory) {
return function enforcedReturnValue() {
var result = instanceInjector.invoke(factory, this);
if (isUndefined(result)) {
throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
}
return result;
};
}
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
function value(name, val) { return factory(name, valueFn(val), false); }
function constant(name, value) {
assertNotHasOwnProperty(name, 'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
1.supportObject函數
這里先引用一下reverseParams的實現
function reverseParams(iteratorFn) {
return function(value, key) { iteratorFn(key, value); };
}
來看看下面的代碼將會得到什么,就知道這個函數在做什么了:
function agent(key,vlue){
console.log(key + '--->'+ value);
}
var new_agent = supportObject(agent);
new_agent({a:123,b:456,c:'abc'});
new_agent('key','value');
2.provider函數,參數name,provider_
作用,創建服務的提供者,serviceProvider,並且用providerCache保存起來。
3.enforceReturnValue
直接調用instanceInjector.invoke來生成服務。
4.factory
調用provide函數,由函數自身提供一個Provider。
5.service,繼續簡化服務的定義。
這意味者,可以給service的第二參數傳遞一個構成函數,service會利用構造函數“new”出一個服務對象來。如果你已經有一個構造函數,需要定義這個構造函數生成的對象為服務,可以考慮使用這個方法。
6.value,繼續簡化服務的定義。
第二個參數,是一個對象(可以是一個基礎類型的值)或者是一個可以返回對象的函數。如果你定義的服務是一個對象,可以考慮用這個方法。
7.constant
可以看到providerCache和instanceCache中的存儲用的鍵是一個,就是說,通過這個函數定義的服務,可以在模塊的config階段和run階段同時有效。
8.decorator,裝飾模式的實現
通過代碼分析,可以得到結論:decorator可以對一個已有的服務進行重新裝飾。
舉例
decorator('exist_service',function($delegate){
$delegate.add_method = function(){
console.log('this method is added');
};
});
上面的代碼,將會向服務“exist_service”增加一個add_method方法。
在最新的angular版本中,已經可以采用angular.module('xxx').decorator('exist_service',function($delegate){})
的方式來使用decorator,我們講的這個版本,還只能在config中使用。
上一期:angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了
下一期:angular源碼分析:angular的整個加載流程