[AngularJS面面觀] 14. 依賴注入 --- module的定義與實現
從本篇文章開始,會開始系統性地介紹angular是如何實現依賴注入這一重要特性的。
引言
提到依賴注入,有後端背景的開發人員應該不會陌生。比如對於Java開發人員而言,絕大部分都是通過Spring這一框架首先了解到依賴注入這一概念的。所謂依賴注入(Dependency Injection),它其實是一個更大的名為控制反轉(Inverse of Control)概念的一種實現模式。只不過這種實現策略使用地太廣泛了,導致出鏡率非常高從而讓很多人都覺得兩者就是一回事。實際上,它們的關係是這樣的:
控制反轉思想更具有一般性和抽象性,感興趣的同學可以參考另一篇文章控制反轉IoC概念隨想。
就依賴注入,一言以蔽之,它的作用是讓框架幫你處理重要物件的生命週期的管理,不需要你顯式地進行管理(物件構造和銷燬)。這樣能夠讓開發人員能夠專注於應用的業務部分。
angular如何實現模組
在談angular是如何實現依賴注入之前,先看看angular是如何定義與實現模組的。畢竟,一個應用總是離不開模組的,應用使用到的各種服務,如controller,service,factory,provider等等,全部都是需要被定義在一個模組中的。
和angular模組相關的原始碼全部儲存在loader.js
中。不到400行程式碼,註釋佔據了一大半。因此angular中用於實現模組的程式碼其實是非常簡練的,讓我們看看它是如何實現的。
angular全域性物件以及ensure方法
稍微深入一點用過angular的同學們都會知道,框架提供了一個名為angular
element
,以及本文的重點angular.module
方法。
那麼這個全域性物件是如何被創建出來的呢?
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
// 將minErr服務新增到angular全域性物件上
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
// 模組的建立方法...
});
}
可以發現,angular全域性物件通過呼叫ensure(window, 'angular', Object)
得到。
而這個方法的定義也十分簡單,更像是一些基本JavaScript操作的練習。window
上已經存在了一個名為angular的物件,那麼就直接返回。如果不存在則呼叫傳入的factory方法進行構造並返回。因此,通過ensure方法得到的物件能夠保證全域性唯一性。
另外一些細節,比如前面用於宣告異常如何處理的兩行:
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
關於minErr
,它是angular中提供的一個用於異常處理的服務。具體可以參看這篇文章,進行了詳細介紹。
這裡定義了兩個異常包裝型別,$injectorMinErr
和ngMinErr
。前者用於包裝一些和injector
相關的異常。關於injector
,是實現依賴注入的關鍵,會在下篇文章中開始介紹。後者用於包裝一些angular的基礎異常。
模組註冊和獲取
至於angular.module方法,也是通過ensure方法來保證其在angular物件上的全域性唯一性:
return ensure(angular, 'module', function() {
// 用來儲存所有的模組
var modules = {};
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
var moduleInstance = {
requires: requires,
name: name,
// 模組物件提供的方法和物件定義在這裡...
};
return moduleInstance;
// ...
});
};
});
以上程式碼是setupModuleLoader函式的返回部分,清晰地表明瞭同樣通過使用ensure函式,來保證了module函式在angular物件上的全域性唯一性。
而其內部的function module(name, requires, configFn)
就開始定義module函式本身的行為了。看到引數名稱是不是很熟繫了。第一個引數name
用來定義模組的名稱,二個引數requires
是一個數組物件,用來定義該模組以來的模組名稱。這個知識點只要是angular的入門文章或者書籍都會介紹吧。
這部分程式碼不停地在巢狀返回貌似很複雜的東西,比如上面的return function module(name, requires, configFn)
,而這個函式內部又返回了一個:ensure(modules, name, function(){ //... })
。看樣子是怪嚇人的,但是把握一條原則,呼叫ensure方法得到的只是一個物件,因此ensure(modules, name, function(){ //... })
返回的只不過是modules
物件的上的一個名為name
的物件。而modules
物件,其實就是用來儲存所有模組的一個字典物件,模組的名字name
作為其鍵值。所以很明顯的,它返回的就是註冊後得到的物件,典型的比如我們在初始化一個angular應用時所做的:
var app = angular.module('testApp', []);
得到的app物件即為我們定義的模組,也就是ensure(modules, name, function(){ //... })
的返回值,相信沒有同學沒寫過上述程式碼吧。清楚了這個脈絡,開始看看一些實現細節:
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
首先會保證hasOwnProperty
這個名字是一個不合法的module名稱。如果真碰到有不懷好意的開發者使用hasOwnProperty
作為模組名稱,那麼angular直接就通過ngMinErr
丟擲異常宣佈罷工不幹了。
然後,就是通過判斷是否傳入了requires
這個陣列來決定是正在新建模組呢,還是正在獲取已經存在的模組。如果存在requires
,那麼直接將當前可能存在的另外一個同名的模組置為空。這一個細節需要引起我們的注意。有些初學者沒有分清楚註冊模組和獲取模組的區別,在獲取模組的時候也傳入了requires
引數,這會導致不斷地註冊新的模組,而在原來的模組上定義的controller
等等服務就都會隨著這個置空操作而灰飛煙滅。所以就會出現莫名其妙的各種”未定義”異常。
最後,就是返回模組。如果是註冊模組,就返回新註冊的模組;如果是獲取模組,就返回獲取到的模組。這一切都是通過早先介紹的ensure函式完成的:
return ensure(modules, name, function() {
var moduleInstance = {
requires: requires,
name: name,
// 模組物件提供的方法定義在這裡...
};
return moduleInstance;
// ...
});
moduleInstance
就代表了這個模組,它將模組的名稱以及依賴直接通過name
和requires
定義在其之上。當然,出了這兩個最最基礎的屬性,moduleInstance
還包含了很多東西,我們日常使用的諸如controller
,service
,factory
,constant
等方法都定義在它之上。它們的實現方式是我們即將討論的內容。
至此,angular中和模組相關的操作就討論完畢了。它作為後續即將介紹的angular核心功能之一依賴注入的一個鋪墊。