angulrJS依賴注入(三)-$inject

Dependency Injection (DI,依賴注入)是一種軟件設計模式,用於處理如何讓程序獲得其依賴(對象的)引用。

依賴注入簡述 | DI in a nutshell

對象或者函數只有以下3種獲取其依賴(的對象)引用的方式:

  1. 依賴可以被使用者自己創建,通過 new 操作符.
  2. 依賴可以通過全局變量(如 window)來查找並引用
  3. 依賴可以在需要的地方被傳入

前兩種創建或查找依賴的方式並不是最優的,因爲他們對依賴進行了硬編碼. 這就使得當依賴變得不可用時,要修改依賴相關的代碼變得非常困難和繁瑣。在測試中更是有問題,因爲通常需要通過模擬依賴來進行隔離測試。
第三種選擇可以說是最可行的方式,因爲它從組件中消除了查找依賴位置的責任。只需要簡單地將依賴傳遞給組件即可。

要管理依賴創建的責任,每個 Angular 應用程序都有一個 injector 。該injector是一個 service locator(定位器) ,負責創建並查找依賴。
下面是一個使用 injector服務的示例:

    / 在一個模塊中提供連接信息  
    angular.module('myModule', []).  

      // 告訴 injector 如何去構建一個 'greeter'  
      // 注意, greeter 自身是依賴於 '$window' 的  
      factory('greeter', function($window) {  
        // 這是一個 factory function,   
        // 職責是爲創建  'greet' 服務.  
        return {  
          greet: function(text) {  
            $window.alert(text);  
          }  
        };  
      });  

    // 新的 injector 從  module 創建.   
    // (這通常由 angular bootstrap 自動創建)  
    var injector = angular.injector(['myModule', 'ng']);  

    // 從 injector 獲取所有依賴  
    var greeter = injector.get('greeter');  

要解決依賴關係硬編碼的問題,也就意味着 injector 需要貫穿整個應用程序生命週期。傳遞 injector 打破了 得墨忒耳定律(Law of Demeter, 最少知識原則)。爲了彌補這一點,在下面的例子中,我們通過依賴聲明的方式將查找依賴的職責交給了 injector:

HTML代碼:

[html] view plain copy 在CODE上查看代碼片派生到我的代碼片
<!-- Given this HTML -->  
<div ng-controller="MyController">  
  <button ng-click="sayHello()">Hello</button>  
</div>  
Angular代碼
[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
// 這是 controller 定義  
function MyController($scope, greeter) {  
  $scope.sayHello = function() {  
    greeter.greet('Hello World');  
  };  
}  

// 由 'ng-controller' directive 在後臺執行  
injector.instantiate(MyController);  

注意通過 ng-controller實例化此類,它可以 在 controller 不知道有 injector 的情況下滿足MyController 所有的依賴。這是最好的結果。應用程序代碼簡單地要求所需的依賴項,無需和 injector 打交道。這個設置不違背 得墨忒耳定律。

依賴註解 | Dependency Annotation

injector 怎麼知道需要注入何種 service 呢?
爲了解決依賴關係,應用程序開發者需要提供 injector 需要的 annotation 信息。在 Angular 中,某些API函數通過使用 injector 來調用,請按照API文檔。injector 需要知道注入哪些服務給函數。下面是通過 service name 信息對代碼進行註解的三種等價方式。他們都是等價的,你可以在適當的地方互換使用.

推斷依賴關係 | Inferring Dependencies

最簡單的獲取依賴的方式,就是讓函數參數名和依賴的名字一致。

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
function MyController($scope, greeter) {  
  ...  
}  

給定一個 function, injector 通過檢查函數聲明和提取參數名稱可以推斷出 service 的名稱 。在上面的例子中, $scope 和 greeter 是需要注入 function 的兩個 services。
雖然簡單直接, 但這種方法在 JavaScript 壓縮/混淆 時會失效,因爲會重命名方法的參數名。這使得這種註解方式只適用於 pretotyping, 或者 demo 程序中。

$inject 註解 | $inject Annotation

爲了可以在壓縮代碼後依然可以注入正確的 services, 函數需要通過 inject. inject 屬性是一個數組,包含 需要注入的 service 名字.

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
var MyController = function(renamed$scope, renamedGreeter) {  
  ...  
}  
MyController['$inject'] = ['$scope', 'greeter'];  

在這種情況下,$inject數組中的值的順序必須和要注入的參數的順序一致。使用上面的代碼片段作爲一個例子, ‘scoperenamed scope”, 而“greeter” 將注入到 “renamedGreeter”。再次提醒注意 $inject 註解必須和 函數聲明時的實際參數保持同步(順序,個數…)。對於 controller 聲明,這種註解方法是很有用的,因爲它將註解信息賦給了 function。

內聯註解 | Inline Annotation

有時候並不方便使用 $inject 註解,比如在註解 directives的時候。
比如下面的示例:

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
someModule.factory('greeter', function($window) {  
  ...  
});

因爲需要使用臨時變量,導致了代碼膨脹爲:

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
var greeterFactory = function(renamed$window) {  
  ...  
};  
greeterFactory.$inject = ['$window'];  
someModule.factory('greeter', greeterFactory);  

這也是提供第三種註解方式的原因.

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
someModule.factory('greeter', ['$window', function(renamed$window) {  
  ...  
}]);

記住,所有的 annotation 風格都是等價的,在 Angular 中,只有支持注入的地方都可以使用.

什麼地方應該使用DI | Where can I use DI?

DI在 Angular 中無處不在。它通常用於 controllers 和工廠方法。

控制器中使用DI | DI in controllers

Controllers 類負責應用程序的行爲。聲明 controllers 的推薦的方法是使用數組表示法:
[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {  
  ...  
  $scope.aMethod = function() {  
    ...  
  }  
  ...  
}]);

這避免了爲 controllers 創建全局函數,並且在代碼壓縮時繼續可用.

工廠方法 | Factory methods

工廠方法在 Angular 中負責創建大多數的對象。例子是 directives, services, 以及 filters。工廠方法被註冊到模塊, 聲明工廠的推薦方法是:

[javascript] view plain copy 在CODE上查看代碼片派生到我的代碼片
angular.module('myModule', []).  
  config(['depProvider', function(depProvider){  
    ...  
  }]).  
  factory('serviceId', ['depService', function(depService) {  
    ...  
  }]).  
  directive('directiveName', ['depService', function(depService) {  
    ...  
  }]).  
  filter('filterName', ['depService', function(depService) {  
    ...  
  }]).  
  run(['depService', function(depService) {  
    ...  
  }]);  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章