這篇文章是探索對於Angular開發者來說既熟悉又陌生的概念:依賴注入。
我們在定義控制器、服務(甚至是指令)的時候通常會依賴一些服務,例如下面這樣:
1 2 3 4 5 6 7 8 9 |
//按照Angular的嚴格模式,希望我們這樣寫: angular.module('app', []).controller('ctrl', ['$scop', $http', function($scope, $http) { ... }]) //在非嚴格模式下也可以這樣寫減少程式碼量: angular.module('app', []).controller('ctrl', function($scope, $http) { ... }) |
實現過程
按照科學方法(提出假設——>進行實驗——>得出結論)可以先猜想一下Angular的實現過程:
- 解析引數。
提取所需的服務(即函式引數)顯得有點麻煩,因為js並沒有提供原生的方法,可能需要手動實現。
一種常用的解析字串的方法就是用正規表示式。
- 例項化服務
得到了引數之後就需要根據引數來生成其對應的服務例項了,因為Angular的服務是單例模式,所以很可能會用到快取的方式來來避免多個相同的服務。
- 執行定義函式
傳入服務並呼叫我們的宣告函式,建立對應的控制器/服務/指令等。
原始碼探究
依賴解析
我們以(Angular1.3)[https://cdn.bootcss.com/angular.js/1.3.20/angular.js]版本原始碼為例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 第3492行 // var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; // fn為傳入的定義函式,通過toString函式來獲取它的原始碼 // 這裡的主要目的是去除函式原始碼中的註釋 fnText = fn.toString().replace(STRIP_COMMENTS, ''); // var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; // 將函式定義的頭部取出來,捨棄函式體,得到 function abc(...) 這種形式 argDecl = fnText.match(FN_ARGS); // var FN_ARG_SPLIT = /,/; // 通過逗號分隔引數進行遍歷,並將每個引數放入陣列中 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { // var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; arg.replace(FN_ARG, function(all, underscore, name) { $inject.push(name); }); }); |
簡而言之,這一部分程式碼流程也很簡單:獲取函式原始碼,取到函式頭部,分割放入陣列。
這樣就獲得了我們依賴注入所需要的引數名,這是針對前面所說的第二種情況,第一種情況則很簡單,直接從陣列中提取就行。
服務例項
1 2 3 4 5 6 7 8 9 10 11 12 |
// 第4201行 //遍歷函式依賴的服務 for (i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; //從快取中獲取或者從provider中獲取服務 args.push( locals && locals.hasOwnProperty(key) ? locals[key] // getService函式負責從provider中獲取對應的定義函式 : getService(key, serviceName) ); } |
這一部分程式碼完整的貼出來看起來會比較費解,因為它的執行方式很像遞迴函式。
簡而言之就是執行服務的定義函式,然後在執行環境(this指標上)繫結對應的函式,然後返回改執行環境。
這樣依賴它的函式就可以訪問服務內部的函式了
同時也用了一個json物件來(locals變數)快取服務。
呼叫函式
1 2 |
// 第4219行 return fn.apply(self, args); |
結論
從擷取的部分原始碼分析來看,基本上如開始所想。解析依賴服務,然後以回撥的方式找到所需服務並呼叫函式。
原始碼地址:
https://cdn.bootcss.com/angular.js/1.3.20/angular.js
參考文章:
《The “Magic” behind AngularJS Dependency Injection》
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式