AngularJS系統學習之Module(模塊)


本文源自:http://blog.csdn.net/woxueliuyun/article/details/50962645

學習之后略有所得, 來此分享。建議看原文。

模塊是提供一些特殊服務的功能塊,比如本地化模塊負責文字本地化,驗證模塊負責數據驗證。一般來說,服務在模塊內部,當我們需要某個服務的時候,是先把模塊實例化,然后再調用模塊的方法。但Angular模塊和我們通常理解的模塊不一樣,Angular模塊只保留服務的聲明,服務的實例化是由服務注入器完成的,實例化之后服務就留在了服務注入器中,和模塊沒有關系了,這就是為什么我們使用的服務全部來自注入器的原因。

 

每調用一次angular.boostrap()方法會創建一個新的Angular應用和一個新的服務注入器,因此,每個應用都對應一個服務注入器,彼此互不沖突。

在angular中,模塊可以是對象、方法(如果是數組,數組的最后一個元素必須是方法,前面的元素都是方法按順序排列的參數名稱)。后面講的模塊屬性和方法,都屬於通過angular.module()定義的模塊對象。如果模塊是方法,是不需要經過angular.module()定義的,只需寫入依賴數組(就是說依賴數組的元素可以是方法),模塊在加載依賴關系的時候直接執行了。

注意:通過angular.module()方法定義的模塊是唯一的,如果重復定義就會覆蓋前面的定義。

angular模塊

angular模塊通過angular.module(name,requires, configFn)方法生成:

  • 參數name是模塊名稱;
  • 參數requires表示依賴模塊數組。如果不設置requires參數,調用angular.module(name)方法表示獲取這個模塊;因此,如果確定新模塊沒有依賴關系,必須設置requires為空數組[];
  • 參數configFn是方法或數組,負責在模塊初始化時做一些配置,如果是數組,最后一個元素必須是方法。

方法configFn並不是在執行angular.module()的時候立即執行,而是當這個模塊被第一次使用時,由注入器調用執行。同時,查看方法configFn中的this就會發現,這個this在瀏覽器中指向的是window,而不是module。而且,方法configFn只會執行一次,因此同一個angular模塊不會重復配置。

參數requires中的字符串表示依賴的模塊名稱。如果不是字符串,則必須是方法(或數組格式的方法),那么,這個方法就代表了一個模塊。

 

同名模塊

已經初始化的angular模塊保存在一個叫modules的緩存對象中,key是模塊名,value是模塊對象。所以,定義一個同名的模塊,等於覆蓋之前的模塊。

服務注入

前面已經講了,angular模塊只保留服務的定義,現在我們再來理解服務是如何加入注入器的。

在了解服務注入器前,還需要了解另一個概念,就是服務提供商,在Angular中稱為Provider,幾乎所有的服務(除了$injector)都是由服務提供商供應。無論是服務還是服務提供商,他們在Angular中都是唯一的,服務和服務提供商是一個一對一的關系。也正是因為這個關系,我們在使用模塊service()、value()等方法時,感覺我們定義的好像只是服務,但其實Angular背后為這些服務都創建了一一對應的服務提供商。注入器的機制就是當我們需要某個服務的時候,首先根據服務名找到對應的服務提供商,然后由服務提供商創建對應的服務並返回。

所以這就是整個過程:

  1.  模塊定義服務、服務提供商;
  2.  注入器根據模塊依賴關系加載模塊,實例化所有服務提供商;
  3.  應用需要服務,注入器根據服務名尋找服務提供商,服務提供商實例化服務。

以上只是理論,現在從代碼層面來看Angular是如何實現的。

每個angular模塊內置有三個數組,invokeQueue保存如何注入服務提供商和值的信息;configBlocks保存模塊的配置信息;runBlocks保存這個模塊的執行信息。模塊被使用的時候,注入器根據invokeQueue中的信息,實例化服務提供商;根據configBlocks中的信息對服務提供商做一些額外的處理;根據runBlocks中提供的信息,調用前面的服務提供商提供的服務執行模塊需要完成的工作。

Angular模塊

angular模塊提供了很多方法來填充這三個數組,比如config()、run()等。三個數組的元素也是數組,具體元素格式參考后面的說明。

模塊方法

調用隊列 – invokeQueue

數組元素為[‘provider’, ‘method’, arguments]。

舉例– 添加一個Controller:

1 angular.module('ngAppDemo',[])  
2   
3 .controller('ngAppDemoController',function($scope) {  
4   
5       $scope.a= 1;  
6   
7       $scope.b = 2;  
8   
9 });  
View Code

這段代碼等於:

 1 invokeQueue.push(['$controllerProvider','register', ['ngAppDemoController', function(){}]]);  

注入器根據這個信息,就會調用$controllerProvider的register方法注冊一個ngAppDemoController。  

angular模塊實例屬性和方法

屬性

requires

表示模塊的依賴數組,即angular.module方法的requires參數。

name

模塊的名字。

_invokeQueue、_configBlocks、_runBlocks

分別對應invokeQueue、configBlocks、runBlocks。

方法

模塊的以下方法最后全部會返回模塊實例本身,形成執行鏈

animation()

調用這個方法表示這個模塊將在$animateProvider中注冊一個動畫服務。

原理:在invokeQueue尾部插入['$animateProvider', 'register', arguments]。

config()

調用這個方法,表示給這個模塊追加一個配置方法。

原理:在configBlocks尾部插入['$injector','invoke', arguments]。

constant()

調用這個方法表示這個模塊將給默認的$provider注冊一個常量。

原理:在invokeQueue首部插入['$provide', 'constant', arguments]。

controller()

調用這個方法表示模塊將在$controllerProvider中注冊一個控制器。

原理:在invokeQueue尾部插入['$controllerProvider', 'register', arguments]。

directive()

調用這個方法表示這個模塊將在$compileProvider中注冊一個指令。

原理:在invokeQueue尾部插入['$compileProvider', 'directive', arguments]。

factory()

調用這個方法表示這個模塊中將生成一個服務工廠(隱式創建一個了服務提供商)。

原理:在invokeQueue尾部插入['$provide', 'factory', arguments]。

filter()

調用這個方法表示這個模塊將在$filterProvider中注冊一個過濾器。

原理:在invokeQueue尾部插入['$filterProvider', 'register', arguments]。

provider()

調用這個方法表示這個模塊將添加一個服務提供商。

原理:在invokeQueue尾部插入['$provide', 'provider', arguments]。

run(block)

調用這個方法表示這個模塊將執行某個功能塊,block可以是方法,也可以是數組。

原理:在invokeQueue尾部插入block。

service()

調用這個方法表示這個模塊將注冊一個服務(隱式創建了一個服務提供商)。

原理:在invokeQueue尾部插入['$provide', 'service', arguments]。

value()

調用這個方法表示這個模塊將注冊一個變量(隱式創建了一個服務提供商)。

原理:在invokeQueue尾部插入['$provide', 'value', arguments]。

服務注入器(Service Injector) & 服務提供商(Service Provider)

在Angular中,服務可能是對象、方法、或者一個常量值。服務由服務提供商創建,而服務提供商由注入器統一管理。當我們需要某個服務的時候,注入器負責根據服務名尋找相應的服務提供商,然后由服務提供商的$get()生產工廠創建服務實例。因此,服務提供商必須有一個$get()方法,這個方法就是服務創建單例工廠。

背后原理:注入器中的Providers和Services各自通過一個Map對象保存在緩存(分別對應providerCache和instanceCache)中,只不過Providers的key是serviceName + “Provider”,而Services的key是serviceName。

服務提供商-Provider

Provider即服務提供商,必須有一個$get()方法,$get()的返回值是Provider在注入器中實際的服務。

注入器

創建注入器的方法只在bootstrap()方法中被調用過,也就是說,每一個angular應用對應一個注入器。

注入過程

注入器由angular.injector(modulesToLoad, isStrictDi)方法創建,在angular中其實為createInjector方法。參數modulesToLoad是數組,元素格式為以下之一:

  • ‘module’,模塊的名稱。
  • [‘service1’, ‘service2’, fn]。
  • fn,方法的返回值必須仍然是方法。

方法angular.injector()的執行過程:

1.        遍歷modulesToLoad,根據moduleName尋找或生成相應的模塊。

2.        調用模塊invokeQueue中的所有方法,這一步的目的是創建必需的服務。

3.        調用模塊configBlocks中的所有方法,目的是用已有的服務配置這個模塊。

4.        調用注入器的invoke()方法執行模塊runBlocks的所有方法。

5.        返回服務注入器實例。

不是所有模塊都是對象,如果模塊本身是方法或者是數組(最后一個元素必須是方法),則運行這個方法、或數組的最后一個方法,相當於直接進入了第四步。

注入器與bootstrap的關系

創建注入器的方法在angular.js中只在bootstrap()方法中被調用過,也就是說,每一個angular應用對應一個注入器。

注入器方法

在providerCache中和instanceCache中分別內置有一個$injector對象,分別負責給模塊注入服務提供商和為方法注入服務。一般我們只談論instanceCache中的$injector對象,因為providerCache和它的$injector是私有的,只在Angular內部代碼使用。

比如,執行模塊調用隊列、配置隊列中的方法時注入的是服務提供商,而當調用運行隊列中的方法時,注入的是服務。

$injector.invoke(fn, self, locals, serviceName)

執行方法fn。locals是可選參數,是對象,表示局部變量。self是fn中的this。

最后一個參數serviceName是可選參數,表示在哪個服務中調用了fn方法,用於錯誤信息顯示,沒有處理邏輯。

$injector.instantiate(Type, locals, serviceName)

l  如果參數Type為方法,根據Type的prototype創建一個實例(通過Object.create方法創建),如果Type是數組,使用最后一個元素的prototype。

l  參數Locals是當Type方法的參數出現在locals對象中的時候,取locals[arg]的值重新作為Type的參數。如果locals中沒有,則等價於調用get(arg,serviceName)獲取service作為新的參數。

實例化過程可以簡單概括為

 1 Type.apply(Object.create(Type.prototype),locals[argName]|| get(argName, serviceName)) 

注意:實例化出來的不一定是對象,也可能是方法。

最后一個參數serviceName是可選參數,表示在哪個服務中實例化了Type,用於錯誤信息顯示,沒有處理邏輯。

$injector.get(name, caller)

從注入器中獲取一個服務實例。

參數name是服務的名稱。參數caller也是字符串,表示調用這個服務的是哪個方法,用於錯誤信息提示,沒有處理邏輯。

$injector.annotate(fn[,strictDi][,name] )

返回數組,數組的元素是fn方法需要注入的依賴服務。

在嚴格模式下,方法的依賴注入必須使用顯示的注解加入,也就是說通過fn.$injector能夠獲取這個方法的依賴注入。

參數name是可選的,用於錯誤顯示,沒有處理邏輯。

方法annotate()也可以接受數組,數組的最后一個參數一定是fn,前面的元素則是依賴。

$injector.has(name)

檢查該注入器中是否存在指定的服務。

Provider方法

注入器的providerCache中內置有一個$provider對象,這是注入器的默認服務提供商,$provider有六個固定的方法。這幾個方法的作用主要是為注入器添加其他服務提供商。

注意:

  • 以下所有方法的name參數不需要以“Provider”結尾,因為provider()方法會默認把這個后綴加上。
  • 以下任何一個方法不做同名判斷,因此,如果出現同名,后者將覆蓋前者。

$provider.provide(name, provider)

參數provider可以是方法或數組,也可以是對象。

l  如果是方法,則是provider的構造函數。調用注入器的instantiate()方法,生成一個provider實例,並以name為key保存在注入器的providerCache中。

l  如果是數組,最后一個必須是provider的構造函數,前面的就是構造函數的參數名。之后的原理和provider是方法的情形相同。

l  如果是對象,說明這個provider已經被實例化了,只需有$get()方法即可。

$provider.factory(name, factoryFn, enforce)

使用$provider.provide()一般需要定義一個Provider類,如果不想定義Provider類,而是直接定義服務工廠,就可以使用這個方法。

背后原理:首先生成一個匿名對象,這個對象的$get屬性就是factoryFn(enforce為false的情況下),然后把這個匿名對象作為$provider.provide()方法的第二個參數。所以,factoryFn其實依然是綁定在一個provider上的。

$provider.service(name, constructor)

調用injector.instantiate()方法,利用參數constructor生成service實例,參數name是這個service的名稱。

眾所周知,service由provider提供,那這個方法是怎么回事?原理:Angular首先根據constructor生成一個factoryFn,然后調用$provider.factory(name, factoryFn)。所以其實還是生成了一個provider。

舉例:

$provider.service('filter', constructor)

等於創建了一個filter服務實例,並且在providerCache中保存了一個名稱為“filterProvider”的服務提供商。      

$provider.value(name, value)

這個方法實際調用injector.factory(name,valueFn(value), false)方法實現。所以其實等於創建一個只提供值的服務提供商。

$provider.constant(name, value)

這個方法直接在providerCache中添加一個屬性實現。

$provider.decorate(serviceName, decorFn)

修改舊的服務,改為執行decorFn方法,並把servcieName原來的服務作為一個參數,參數名為$delegate。等價於一個靜態代理。

背后原理:首先根據seviceName找到Provider,然后修改provider的$get屬性。

服務實例化

所有服務都由Provider管理,除了常量值,其它一般都是還沒有創建出來的。了解了注入器的所有方法,也應該可以看出,只有兩個方法:get()和invoke()真實的調用了服務。其實,服務的實例化實際是在注入器的get()方法中完成的,而invoke()只是在需要的時候調用了get()。

angular內置模塊

ngLocale - 本地化模塊

angular.module('ngLocale',[]).provider('$locale', $LocaleProvider);

結果是invokeQueue.push(['$provide', 'provider',['$locale', $LocaleProvider]]);

ng

angular.module('ng',['ngLocale']).config(['$provide', function(){}]);

結果是configBlocks.push(['$injector', 'invoke',['$provide', function(){}]])。

三個固定模塊

每個使用bootstrap(element, modules, config)生成的應用,注入器中有三個固定的模塊:

  • 第一個模塊是"ng"。
  • 第二個模塊是[‘$provider’,fn],它的作用是把根元素element作為變量保存在$provider中。
  • 第三個模塊是[‘$compileProvider’,fn],它的作用是根據config.debugInfoEnabled調用 $conpileProvider.debugInfoEnabled(true)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM