Angular依賴注入詳解
來自: http://www.cnblogs.com/leonwang/p/5178551.html
Angular算是將后端開發工程化引入前端的先驅之一,而Dependency injection依賴注入(后面簡稱為DI)又是Angular內部運作的核心功能,所以要深入理解Angular有必要先理解這一核心概念。
維基百科對依賴注入的解釋
在軟件工程中,依賴注入是實現控制反轉的一種軟件設計模式,一個依賴是一個被其他對象(client)調用的對象(服務),注入則是將被依賴的對象(service)實例傳遞給依賴對象(client)的行為。將 被依賴的對象傳給依賴者,而不需要依賴者自己去創建或查找所需對象是DI的基本原則。 依賴注入允許程序設計遵從依賴倒置原則(簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合) 調用者(client)只需知道服務的接口,具體服務的查找和創建由注入者(injector)負責處理并提供給client,這樣就分離了服務和調用者的依賴,符合低耦合的程序設計原則。
依賴注入中的角色
從維基百科解釋可知, DI中包含三個角色,調用者(client), 服務(service)和注入者 (injector),下面開始介紹本文的主題 Angular的依賴注入。
Angular依賴注入分析
先看看下面這段 hello,world代碼 (注意:設置了嚴格模式或壓縮混淆代碼后 下面的代碼不能正常工作,后面有解釋)
angular.module('myApp', []) .controller('Ctl', function ($scope, $log) { $scope.name = 'leonwgc'; $log.log('hello,world'); });
上面這段代碼就用到了angular的依賴注入,代碼首先創建了一個myApp模塊,然后在此模塊中創建了Ctl控制器,創建控制器函數的第二個參數則是控制器的構造函數, 構造函數聲明了對$scope和$log服務的依賴。 當構造函數執行時, 即可獲得$scope和$log服務實例,進行操作。 從我們前面對DI的了解,$scope和$log是由注入器injector 提供,知道了injector的存在,我們直接從angular的源碼中將其找出,如下:
function createInternalInjector(cache, factory) { // 中間一段略去... // 調用client function invoke(fn, self, locals, serviceName) { if (typeof locals === 'string') { serviceName = locals; locals = null; } var args = [], // 查詢依賴 $inject = createInjector.$$annotate(fn, strictDi, serviceName), length, i, key; // 中間一段略去... // 遍歷$inject數組調用getService獲取服務.... //開始執行client , args則是依賴的全部服務,injector都為我們創建好了 return fn.apply(self, args); } // 中間一段略去... // 這里返回公開的injector對象 return { // 執行DI方法,比如上面的控制器函數 // invoke方法首先就是調用annotate取得依賴 // 然后調用get取得服務 // 如果緩存中沒有服務,get內部調用instantiate創建服務并緩存 // 最后利用function.apply傳入依賴并執行 invoke: invoke, // 實例化(創建)服務 instantiate: instantiate, // 獲取服務(如果緩存中有,直接從緩存拿,沒有則調用instantiate創建并放入緩存,下次直接從緩存拿) get: getService, // 獲得依賴服務 annotate: createInjector.$$annotate, // 檢查緩存中是否包含服務 has: function(name) { return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } }; }
源碼中查詢依賴的源碼如下:
function annotate(fn, strictDi, name) { var $inject, fnText, argDecl, last; if (typeof fn === 'function') { // 如果我們直接給函數添加了$inject依賴 // 則直接返回依賴,后面不做處理 if (!($inject = fn.$inject)) { $inject = []; if (fn.length) { if (strictDi) { if (!isString(name) || !name) { name = fn.name || anonFn(fn); } throw $injectorMinErr('strictdi', '{0} is not using explicit annotation...', name); } // 針對直接在構造函數中使用服務的情況 // 使用function.toString() 然后正則匹配出依賴的對象 // 所以上面例子如果混淆了代碼就呵呵了 // 最后存入$inject數組 fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); }); }); } //給構造函數添加$inject屬性 fn.$inject = $inject; } } else if (isArray(fn)) { last = fn.length - 1; assertArgFn(fn[last], 'fn'); // 如果是數組格式,則依賴對象是數組的第一個到倒數第二個對象 // 要調用的函數則是數組的最后一個元素 $inject = fn.slice(0, last); } else { assertArgFn(fn, 'fn', true); } // 返回依賴數組 return $inject; }
看了上面的源碼片段和解釋,想必大家對angular的依賴注入有了整體的認識。
下面是另外兩種推薦的聲明依賴的方式
1. 數組注釋 (推薦), js壓縮混淆不會有影響。
angular.module('myApp', []) .controller('Ctl', ['$scope', '$log', function ($scope, $log) { $scope.name = 'leonwgc'; $log.log('hello,world'); }]);
2.$inject 屬性 ,js壓縮混淆不會有影響
angular.module('myApp', []) .controller('Ctl', Ctrl); function Ctrl($scope, $log) { $scope.name = 'leonwgc'; $log.log('hello,world'); } // 給構造函數添加$inject屬性, // $inject是一個數組,元素是依賴的服務名. Ctrl.$inject = ["$scope", "$log"];
Angular引入了大量后端開發的概念,而前端同學可能還不熟悉,望本文能有所幫助。
</div>