作者 CraftsCoder?
冷月無聲 -?博客頻道 - CSDN.NET
??http://blog.csdn.net/jaytalent/article/details/50986402
?
本文結(jié)合一些資料,談談AngularJS的依賴注入機制。主要參考資料有:
1. AngularJS官方文檔:https://docs.angularjs.org/guide/di
2. Github文章:https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
3. 專著:Pro AngularJS
4. AngularJS 源代碼
一、關于依賴注入
依賴注入式AngularJS的重要特性之一,有關概念和定義參考維基百科。依賴注入簡化了Angular解析模塊/組件之間依賴的過程。通常一個組件要獲得它的依賴,有三種方式:
?
直接創(chuàng)建出依賴,如使用new操作符能夠查找到依賴,如引用全局變量在需要的地方傳入依賴第三種的優(yōu)勢在于組件省去了定義/定位依賴的過程,也使得依賴的耦合度降低,可擴展性更強。依賴注入主要有兩種形式(看這里):setter注入和constructor注入。前者的代表是Spring框架的setter方式,AngularJS則使用的是constructor注入。這里簡單說一下二者的區(qū)別,setter注入顧名思義,首先使用一個無參的默認構(gòu)造器構(gòu)造對象,然后使用setter方法將依賴注入到新對象中。這種方式的一個缺陷是在編譯時并不知道對象之間的依賴關系,依賴解析由某種框架在運行時完成。如果在配置依賴時有遺漏,則在運行時會報空引用錯誤。對于constructor注入,在構(gòu)造對象時,將依賴的組件以參數(shù)形式傳入構(gòu)造器,傳入組件所依賴的組件以相同的方式構(gòu)造,依此類推,確保依賴鏈條最頂端的組件首先構(gòu)造,一直到當前需要注入的對象構(gòu)造完畢。該方式避免了依賴的遺漏。
二、AngularJS中的依賴注入 Angular的injector子系統(tǒng)負責創(chuàng)建組件,解析依賴,并將其按需提供給其他組件。每個Angular應用都有一個injector。Angular
在應用的啟動階段(bootstrap,參見前面的文章)會創(chuàng)建一個injector:
[javascript]?view plaincopy
var?injector?=?angular.injector(['ng',?'myApp']);?? injector方法的數(shù)組參數(shù)定義了可以提供依賴組件的模塊,一般會提供模塊'ng'和應用首先加載的模塊'myApp'。查看源碼發(fā)現(xiàn),injector方法實際上實現(xiàn)為createInjector方法,由bootstrap方法調(diào)用。createInjector內(nèi)部調(diào)用createInternalInjector方法返回真正的injector對象,返回結(jié)果如下:
[javascript]?view plaincopy
return?{????????invoke:?invoke,????????instantiate:?instantiate,????????get:?getService,????????annotate:?createInjector.$$annotate,????????has:?function(name)?{??????????return?providerCache.hasOwnProperty(name?+?providerSuffix)?||?cache.hasOwnProperty(name);????????}??????};?? ?
其中前三個方法很重要,例如可以通過get方法獲得一個需要注入的組件:
[javascript]?view plaincopy
var?comp?=?injector.get('component');?? 在Angular中,依賴注入可謂無孔不入。通常在兩種場景(函數(shù))下會使用到依賴注入:
工廠方法定義的組件(components):如directive,factory,filter,provider,controller等。這些工廠函數(shù)需要注冊到某個模塊上。controller比較特殊,它雖然也是一種組件,但是特別之處是它與某個DOM元素關聯(lián),因此可以注入$scope?service,而其他組件只能注入$rootScope?service。模塊提供的run/config方法。我們稱定義組件的工廠方法和run/config方法是
可注入的。 Angular支持三種定義依賴注入的方式:
數(shù)組標注:最常用且推薦的方式。例如:
[javascript]?view plaincopy
myApp.controller('smallCatCtrl',?['$scope',?function($scope){??????$scope.sayCat?=?function(){??????????alert('I?Love?Circle!');??????}??}]);?? $inject屬性標注:這種方式通過工廠方法的$inject屬性聲明依賴的組件,主要用于js被壓縮/混淆時,變量被重命名的情況。例如:
[javascript]?view plaincopy
var?MyController?=?function($scope,?myService)?{????}??MyController.$inject?=?['$scope',?'myService'];??myApp.controller('MyController',?MyController);?? 隱式標注:這種方法最簡易,Angular會通過工廠方法的參數(shù)名,推斷找到依賴的組件,但不能用于js被壓縮/混淆的情況,因為這種情況下方法的參數(shù)可能被重命名導致Angular無法定位依賴。例如:
[javascript]?view plaincopy
myApp.controller('smallCatCtrl',?function($scope){??????$scope.sayCat?=?function(){??????????alert('I?Love?Circle!');??????}??});?? 另外,Angular支持使用嚴格注入模式:ng-strict-di,該聲明與ngApp位于同一元素。嚴格模式下,不允許隱式注入,如果使用Angular會拋出異常。 要獲得Angular應用的injector,只需注入$injector 即可,有意思的是它知道如何注入自己。通過使用$injector service的invoke方法,可以將組件注入到任何方法中,如:
[javascript]?view plaincopy
var?myFunction?=?function(hello)?{????hello('My?Cat');??};??$injector.invoke(myFunction);?? 這段代碼將hello service注入到myFunction函數(shù)作為其一個參數(shù)。我們定義組件(如controller,directive,filter,factory)所使用的工廠方法就是通過invoke函數(shù)注入依賴的。例如在Angular啟動時,bootstrap方法會有如下邏輯:
[javascript]?view plaincopy
injector.invoke(['$rootScope',?'$rootElement',?'$compile',?'$injector',?????????function?bootstrapApply(scope,?element,?compile,?injector)?{??????????scope.$apply(function()?{????????????element.data('$injector',?injector);????????????compile(element)(scope);??????????});????????}]??????);?? $rootScope等service被注入到bootstrapApply方法中,輔助完成啟動過程。 injector針對每個可注入組件只創(chuàng)建一個實例(調(diào)用injector.instantiate方法),創(chuàng)建之后會將其緩存,以備后續(xù)訪問。如圖:(來源:https://docs.angularjs.org/guide/di)
三、Angular中的可注入組件 前面談到了工廠方法以及config/run方法為可注入方法。下面稍微展開一點。首先從Angular中的
provider說起。provider的概念比較抽象,簡單來說,provider支持為某個組件(如factory,service,value等)在
加載時提供一些配置。最終,每個provider會被注入到特定的組件使用。在Angular中,factory,service和value本質(zhì)上都是一種provider,Angular會執(zhí)行如下代碼定義這些provider:
[javascript]?view plaincopy
myApp.config(function($provide)?{????$provide.provider('sayHello',?function()?{??????this.$get?=?function()?{????????return?function(name)?{??????????alert("Hello,?cat?"?+?name);????????};??????};????});??});?? 這里的config方法稍后再談。$provide 是Angular內(nèi)部提供的一個service,用來定義provider。$get方法返回了真正的service。為了簡單,使用具體的provider類型進行定義也可以,這樣還可省去get方法:
[javascript]?view plaincopy
myApp.config(function($provide)?{????$provide.factory('sayCat',?function()?{??????return?function(name)?{????????alert("Hello,?Cat"?+?name);??????};????});??});?? 這樣就定義了一個factory service。如果把這套定義邏輯抽象出來,就成了Angular模塊的一系列定義service的方法:
[javascript]?view plaincopy
myMod.factory("sayHello",?...);??myMod.service("sayHello",?...);??myMod.value("Cat",?...);?? 這些方法在調(diào)用時實際執(zhí)行仍然是前面代碼段所示的完整版本(有get和provider方法)。
下面說說config/run方法。Angular在加載模塊時經(jīng)過兩個階段:config和run。傳入config函數(shù)的方法會在當前模塊加載時執(zhí)行;傳入run函數(shù)的方法會在所有模塊加載結(jié)束后執(zhí)行。因此,在config階段即可配置多個provider,但是在config階段,只有provider可以注入,因此自定義的service無法注入到config中。這也好理解,因為config階段是對service進行配置的而不是使用service本身。在前面的代碼中,$provide service本身就是一個provider(對于Angular應用來講相當于一個'元provider'),在模塊加載時會調(diào)用$provide的provider方法定義一個新的provider。再看一個具體的例子:
?
[javascript]?view plaincopy
define(['angular'],?function(angular){??????return?angular.module('myCat',?[])??????.provider('hello',?function()?{??????????var?firstName?=?'';??????????this.makeName?=?function(first){??????????????firstName?=?first;??????????}??????????this.$get?=?function()?{??????????????return?function(name)?{??????????????????alert("Hello,?"?+?firstName?+?'??'?+?name);??????????????};????????};??????})??????.config(function(helloProvider){??????????helloProvider.makeName('Circle');??????})??????.controller('catCtrl',?[??????????'$scope',???????????'$q',???????????'hello',??????????function($scope,?$defer,?hello){??????????????$scope.catName?=?"";??????????????$scope.sayHello?=?function(){??????????????????hello('Jiang');????????????};??????????}]??????);??});??
在myCat模塊中定義了一個名為hello的provider。該provider提供了一個makeName配置方法,用于設置firstName。$get方法返回一個函數(shù)用于打印一句話,這是該provider的核心功能(記住provider具體化后仍然是一個service,用于完成某種工作,只不過它是可配置的)。在config方法中,注入的函數(shù)參數(shù)對象名為系統(tǒng)生成的provider的名字helloProvider,自動添加了'provider'后綴。這里調(diào)用配置方法,這個配置會在未來使用該provider提供的功能時生效。最后,在controller定義中,注入了hello provider,并在sayHello方法中調(diào)用之,完成功能。這種模式應用十分廣泛,一個例子是開源項目ui-router中的$state service以及對應的$stateProvider(provider的$get方法返回了一個$state對象,定義了go等方法和屬性,使用過的開發(fā)者應該很熟悉了)。只有provider類型的service可以注入到其他組件中供其使用(如factory,constant等)。
?
?
?
?
?
?
?
?
值得注意的是,可以向controller注入service(可注入),但controller
不能被注入其他組件中,因為
controller本身不是provider。Angular維護著一個service名為$controller,定義并注冊一個controller的工作實際上由該service的provider完成,當需要創(chuàng)建controller實例時,$controller會調(diào)用$injector的invoke方法完成依賴注入,$controllerProvider的$get方法返回一個實例。 類似地,directive對應$compile service,filter對應$filter service,不再詳述。
?
轉(zhuǎn)載于:https://www.cnblogs.com/tongbiao/p/6830118.html
總結(jié)
以上是生活随笔為你收集整理的理解AngularJS中的依赖注入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。