1. 程式人生 > >[AngularJS面面觀] 14. 依賴注入 --- module的定義與實現

[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

的全域性物件,在其之上定義了一些工具方法,DOM元素建立方法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中提供的一個用於異常處理的服務。具體可以參看這篇文章,進行了詳細介紹。

這裡定義了兩個異常包裝型別,$injectorMinErrngMinErr。前者用於包裝一些和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就代表了這個模組,它將模組的名稱以及依賴直接通過namerequires定義在其之上。當然,出了這兩個最最基礎的屬性,moduleInstance還包含了很多東西,我們日常使用的諸如controllerservicefactoryconstant等方法都定義在它之上。它們的實現方式是我們即將討論的內容。

至此,angular中和模組相關的操作就討論完畢了。它作為後續即將介紹的angular核心功能之一依賴注入的一個鋪墊。