1. 程式人生 > >AngularJS 淺談DI-依賴注入 $injector的神奇之處

AngularJS 淺談DI-依賴注入 $injector的神奇之處

前言

依賴注入(DI)和控制反轉(IOC)都是java Spring裡經典的面向物件程式設計的法則來削減計算機程式的耦合問題的解決方案。
在Angular中,引入了DI的思想,DI是一種工具思想,而不是一種目的,它的目的是為了降低程式程式碼之間的耦合。

Angular的啟動方式

先看一下angular究竟是怎麼啟動的。
在等待DOM解析完畢後,觸發DOMContentLoaded事件時,angular會嘗試找到ng-app指令,從而來確定程式的邊界,而angular總是以模組開始啟動的。

一個簡單的angular程式,大概類似於這樣

angular.module("myApp"
,[]) .controller('myController', ['$scope', function($scope){ console.log("hello world"); }]);

上述程式碼比較簡單我這裡就不再贅述了。

然而,有的時候我們並不想顯示的通過ng-app來啟動angular,因此我們可以手動的呼叫angular.bootstrap方法來進行一個啟動的操作,到這裡我們首先必須明白,angular是一個單例物件,被繫結在window這個全域性物件上。

在啟動angular程式之前,我們必須明白的是我們必須等待瀏覽器解析DOM樹完畢後我們才能啟動angular來遍歷DOM樹,由於angular內部封裝有輕量級的jquery,因此我們可以這樣寫

let app = angular.module("myApp",[]);
angular.element(document).ready = () => {
            angular.bootstrap(document,["myApp"]);
        }

這樣我們就手動的啟動了一個angular程式。

三種注入方式

在呼叫angular.bootstrap時,在bootstrap方法內部會建立一個$injector物件,這個物件是個注入器,用於完成依賴注入的核心物件,$injector也是一個單例物件,同時它也是一個service。

現在我們大致的來看一下注入的三種方式。

1.推斷注入法

let app = angular.module('myApp',[]);

let myController = $scope => {
    $scope.name = 'zhangsan';
}

app.controller('myController',myController);

這種注入方式會使得injector去尋找到與引數名相同的服務來注入,它的缺點很明顯,當我們用grunt或者其他打包工具來進行程式碼混淆的時候,它就可能會注入失敗,因為它注入的依據僅僅是引數名這個字串而已,改變了過後就找不到了。

2.宣告式注入法

let app = angular.module('myApp',[]);

let myController = $scope111 => {
    $scope111.name = 'zhangsan';
}

myController.$inject = ['$scope'];

app.controller('myController',myController);

可以看到上面的myController 的引數名已經被改變了,按理說是找不到$scope111這個服務的,但是下面我們用該函式$injector屬性來指向了一個數組,注入器會根據這個陣列,依次傳入函式的引數列表中,因此,就算程式碼被混淆了,也不會有任何問題,那這樣做的缺點是,書寫太過麻煩,每次都要去寫一個$inject 來進行宣告。

內聯式注入法

下面介紹一種比較好的注入方式
正如介紹啟動方式的時候

angular.module("myApp",[])

            .controller('myController', ['$scope', function($scope){
                console.log("hello world");
            }]);

控制器方法的第二個引數接受一個數組,數組裡宣告的是需要被注入的服務名稱,陣列的最後一個元素必須為一個函式,這個的函式的引數列表會根據陣列的前幾個元素找到的對應的服務進行依次注入,這種寫法其實就是第二種注入方式的簡寫。

淺談$injector

$injector是一個angular內建的注入器,我們可以根據需要獲取到注入器的物件。
比如

angular.injector();

這樣的話我們就能拿到一個injector物件。
既然,$injector也是一個service,那麼,我們可以直接通過注入器將它自己注入進來,聽起來很繞?不過確實如此。

    .controller('myController', ['$scope','$injector' function($scope,$injector){
                console.log("hello world");
            }]);

之後我們可以呼叫$injector的get方法,來注入其他的服務。

angular.module("myApp",[])
            .factory('nameObj',() => {
                return {
                    name:'zhangsan'
                }
            })
            .controller('myController',['$scope','$injector',($scope,$injector) => {
                let nameObj = $injector.get("nameObj");
                console.log(nameObj.name);
            }]);

可以看到控制檯清楚的列印了’zhangsan’

可能我們想知道$injector是怎樣知道我們想要的service的,這裡就不得不說$injector的annotate方法,這個方法可以返回一個函式的引數列表,只要拿到引數列表,我們就可以去找到對應的服務來進行注入了。
比如 :

angular.injector().annotate((aaaa,bbb,cc,d) => {});

這樣,我們可以清楚的看到控制檯列印的引數列表

output:
["aaaa", "bbb", "cc", "d"]

這裡不得不順帶的提一下service,service是factory的一層包裝,靈活性較低,實際上在service函式內部,angular去呼叫了$injector的instantiate方法來把傳入的建構函式進行例項化。

從這裡看來,$injector確實給angular帶來了許多的活力,很多時候,我們並不關心我們怎樣得到一個東西,我們只需要關係我們要得到一個什麼樣的東西,中間的過程都由$injector做了,這就是依賴注入(DI)