Angular.JS學習之依賴注入$injector詳析
前言
在依賴注入(IoC)之前,我們在程序中需要創(chuàng)建一個對象很簡單也很直接,就是在代碼中new Object即可,有我們自己負責創(chuàng)建、維護、修改和刪除,也就是說,我們控制了對象的整個生命周期,直到對象沒有被引用,被回收。誠然,當創(chuàng)建或者維護的對象數(shù)量較少時,這種做法無可厚非,但是當一個大項目中需要創(chuàng)建大數(shù)量級的對象時,僅僅依靠程序員來進行維護所有對象,這是難以做到的,特別是如果想在程序的整個生命周期內(nèi)復用一些對象,我們需要自己寫一個緩存模塊對所有對象進行緩存,這加大了復雜度。當然,IoC的好處并不僅限于此,它也降低了對依賴的耦合度,不必在代碼中進行引用或者傳參即可操作依賴。
在js中,我們可以這樣引入依賴
1、使用全局變量引用
2、在需要的地方通過函數(shù)參數(shù)傳遞
使用全局變量的壞處自不必說,污染了全局的名字空間,而通過函參傳遞引用,也可以通過兩種方法實現(xiàn):
1、閉包傳遞
2、后臺解析出依賴對象,并通過Function.prototype.call進行傳參
而在AngularJS中,依賴注入是通過后者實現(xiàn)的,接下來的幾節(jié)將會介紹IoC模塊的具體實現(xiàn)。
獲取依賴
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
// 獲取服務名
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');
function anonFn(fn) {
// For anonymous functions, showing at the very least the function signature can help in
// debugging.
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
args = fnText.match(FN_ARGS);
if (args) {
return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
}
return 'fn';
}
function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
if (strictDi) {
if (!isString(name) || !name) {
name = fn.name || anonFn(fn);
}
throw $injectorMinErr('strictdi',
'{0} is not using explicit annotation and cannot be invoked in strict mode', name);
}
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函數(shù)通過對入?yún)⑦M行針對性分析,若傳遞的是一個函數(shù),則依賴模塊作為入?yún)鬟f,此時可通過序列化函數(shù)進行正則匹配,獲取依賴模塊的名稱并存入$inject數(shù)組中返回,另外,通過函數(shù)入?yún)鬟f依賴的方式在嚴格模式下執(zhí)行會拋出異常;第二種依賴傳遞則是通過數(shù)組的方式,數(shù)組的最后一個元素是需要使用依賴的函數(shù)。annotate函數(shù)最終返回解析的依賴名稱。
注入器的創(chuàng)建
AngularJS的API也提供了injector部分,通過injector部分,通過injector可以使用get,has,instantiate,invoke以及上節(jié)提到的annotate等方法,通過源碼可以更清晰的理解。
function createInternalInjector(cache, factory) {
// 對服務注入器 providerInjector而言,只根據(jù)服務名獲取服務,factory會拋出異常
function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}
function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
}
var args = [],
// 解析并獲取注入服務列表
$inject = annotate(fn, strictDi, serviceName),
length, i,
key;
for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
// 注入的服務作為參數(shù)傳入
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
}
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
}
function instantiate(Type, locals, serviceName) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}
createInternalInjector方法創(chuàng)建$injector對象,傳遞的參數(shù)分別為緩存對象和工廠函數(shù)。在具體實現(xiàn)中,AngularJS創(chuàng)建了兩個injector對象--providerInjector和instanceInjector(這兩個對象的不同主要是createInternalInjector方法傳遞的緩存對象不同),而通過angular.injector()導出的就是instanceInjector。對于providerInjector,主要用來獲取服務的提供者,即serviceProvider。而對于instanceInjector而言,主要用于執(zhí)行從providerInjector獲取的provider對象的$get方法,生產(chǎn)服務對象(依賴),并將服務對象傳遞給相應的函數(shù),完成IoC。
首先從get方法說起,get方法主要獲取指定名稱的服務,通過angular的injector方法獲取的是instanceInjector,而當緩存中沒有該服務對象(依賴)時,我們需要執(zhí)行factory(serviceName, caller)方法,我們看看對于的factory函數(shù):
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function
(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return
instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
}));
紅色部分即為factory函數(shù),它顯示通過providerInjector獲取相應服務的提供者serviceProvider,然后調(diào)用instanceInjector的invoke方法在serviceProvider上下文執(zhí)行serviceProvider的$get方法,返回服務對象并保存在緩存中。這樣,便完成了服務對象(依賴)的獲取和緩存。
invoke方法也很簡單,它的入?yún)⒎謩e問fn, self, locals, serviceName,即要執(zhí)行的函數(shù),函數(shù)執(zhí)行的上下文,提供的options選項和服務名。首先獲取函數(shù)的所有依賴名,通過annotate方法完成之后,如果options中提供了對于名稱的依賴,則使用,否則通過get方法獲取依賴,最后傳入函數(shù),并將函數(shù)的執(zhí)行結(jié)果返回。invoke返回的結(jié)果往往是一個服務對象。
instantiate方法主要根據(jù)提供的構(gòu)造函數(shù)創(chuàng)建一個示例,用作依賴或提供服務。值得一提的是并沒有通過new關鍵字創(chuàng)建對象,而是通過ECMA5提供的Object.create來繼承函數(shù)的原型對象實現(xiàn),非常巧妙。
has方法則是相繼判斷serviceProvider和service是否存在于緩存中。
至此,$injector對象創(chuàng)建完畢。
注冊服務(依賴)
服務不可能憑空而來,我們需要自己實現(xiàn)或者外部引入服務或依賴。所以,注冊服務的模塊也是值得深究的。AngularJS提供了多種注冊服務的API,但是我們著重關注的是provider方法,其他factory,service方法都是基于此進行構(gòu)建的。
這些方法(provider,factory等)綁定在providerCache.provide對象上,而我們通過angular.module(′app′,[]).provider(...)方式調(diào)用的provider函數(shù),會在module加載期間將調(diào)用(該調(diào)用抽象成一個數(shù)組,即[provider,method,arguments])綁定在內(nèi)部的invokeQueue數(shù)組中,最終在providerCache.provide對象上,而我們通過angular.module(′app′,[]).provider(...)方式調(diào)用的provider函數(shù),會在module加載期間將調(diào)用(該調(diào)用抽象成一個數(shù)組,即[provider,method,arguments])綁定在內(nèi)部的invokeQueue數(shù)組中,最終在providerCache.provide對象上調(diào)用provider方法,其他的controller,directive等方法類似,不過是綁定在providerCache.controllerProvider,providerCache.controllerProvider,providerCache.compileProvider對象上。
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;
}
// 在服務實例化之后進行調(diào)用(攔截),攔截函數(shù)中注入實例化的服務,可以修改,擴展替換服務。
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});
};
}
provider方法需要兩個參數(shù),一個是服務名(依賴名),另外是工廠方法或者是一個包含依賴和工廠方法的數(shù)組。首先通過providerInjector創(chuàng)建工廠方法的一個實例,并添加到providerCache中,返回。
factory方法只是將第二個參數(shù)封裝成了一個包含$get方法的對象,即serviceProvider,緩存。并不復雜。
而service方法則嵌套注入了$injector服務,即instanceInjector,它會創(chuàng)建構(gòu)造函數(shù)的實例,作為服務對象。
value方法僅僅封裝了一個provider,其$get方法返回value值。
constant方法則將value的值分別存入providerCache和instanceCache中,并不需要invoke獲取其value值。
而比較特殊且擴展性較高的decorator方法,是在serviceProvider的get方法后面添加一個攔截函數(shù),并通過傳遞依賴get方法后面添加一個攔截函數(shù),并通過傳遞依賴delegate來獲取原先invoke $get方法返回的服務對象。我們可以通過decorator來對服務進行擴展,刪除等操作。
流程
最后,在基本的實現(xiàn)已經(jīng)完成的基礎上,我們走一遍具體的注入流程,更易于我們的深入理解。
angular.module("app",[])
.provider("locationService",function(){
...
})
.controller("WeatherController",function($scope,locationService,$location){
locationService.getWeather()
.then(function(data){
$scope.weather = data;
},function(e){
console.log("error message: "+ e.message)
});
})
我們不關心具體的代碼實現(xiàn),僅僅使用上述代碼作為演示。
首先確定AngularJS上下文的范圍,并且獲取依賴模塊(在此處為空);
繼續(xù)注冊服務(依賴),將serviceProvider緩存至providerCache中;
聲明控制器;
在此獲取injector示例,通過執(zhí)行invoke函數(shù),獲取[“injector示例,通過執(zhí)行invoke函數(shù),獲取[“scope”,”locationService”,”location”]依賴列表,通過location”]依賴列表,通過injector的get方法獲取相應的依賴對象。對于scope和scope和location服務而言,在AngularJS初始化時已經(jīng)注入到Angular中,因此可以獲取相應的provider對象,執(zhí)行相關的方法返回scope和scope和location對象,而locationService則在provider中進行了聲明,因此獲取到locationServiceProvider對象,通過調(diào)用instanceInjector.invoke(locationServiceProvider.$get, locationServiceProvider, undefined, “l(fā)ocationService”)返回locationService對象。
最后將所有的依賴組裝成數(shù)組[scope,locationService,scope,locationService,location]作為參數(shù)傳遞給匿名函數(shù)執(zhí)行。
總結(jié)
至此,依賴注入完成。大家對依賴注入$injector有沒有進一步的了解呢?以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
相關文章
Angular @HostBinding()和@HostListener()用法
本篇文章主要介紹了Angular @HostBinding()和@HostListener()用法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
AngularJS ng-bind-template 指令詳解
本文注意介紹AngularJS ng-bind-template 指令,這里把相關資料整理了一下,并附實例代碼,有需要的小伙伴可以參考下2016-07-07
AngularJS出現(xiàn)$http異步后臺無法獲取請求參數(shù)問題的解決方法
這篇文章主要介紹了AngularJS出現(xiàn)$http異步后臺無法獲取請求參數(shù)問題的解決方法,較為詳細的分析了AngularJS出現(xiàn)異步請求后臺無法解析的原因與相應的解決方法,需要的朋友可以參考下2016-11-11
詳解Webstorm 下的Angular2.0開發(fā)之路(圖文)
這篇文章主要介紹了詳解Webstorm 下的Angular2.0開發(fā)之路(圖文) ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
angular6.0開發(fā)教程之如何安裝angular6.0框架
這篇文章主要介紹了angular6.0開發(fā)教程之如何安裝angular6.0框架,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
angular 數(shù)據(jù)綁定之[]和{{}}的區(qū)別
這篇文章主要介紹了angular 數(shù)據(jù)綁定之[]和{{}}的區(qū)別,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09

