angular 依賴注入原理

依賴注入(Dependency Injection,簡稱DI)是像C#,java等典型的面嚮對象語言框架設計原則控制反轉的一種典型的一種實現方式,angular把它引入到js中,介紹angular依賴注入的使用方式的文章不少,
angular官方的文檔,也有很詳細的說明。但介紹原理的較少,angular代碼結構較複雜,文章實現了一簡化版本的DI,核心代碼只有30行左右,相看實現效果(可能需FQ)或查看源碼javascript

這篇文章用盡可能簡單的方式說一說 angular依賴注入的實現。html

簡化的實現原理

要實現注入,基本有三步:java

  1. 獲得模塊的依賴項
  2. 查找依賴項所對應的對象
  3. 執行時注入

1. 獲得模塊的依賴項

javascript 實現DI的核心api是git

Function.prototype.toString

對一個函數執行toString,它會返回函數的源碼字符串,這樣咱們就能夠經過正則匹配的方式拿到這個函數的參數列表:angularjs

function extractArgs(fn) { //angular 這裏還加了註釋、箭頭函數的處理
            var args = fn.toString().match(/^[^\(]*\(\s*([^\)]*)\)/m);
            return args[1].split(',');
        }

2. 查找依賴項所對應的對象

java與.net經過反射來獲取依賴對象,js是動態語言,直接一個object[name]就能夠直接拿到對象。因此只要用一個對象保存對象或函數列表就能夠了github

 1 function createInjector(cache) {
 2             this.cache = cache;
 3 
 4         }
 5 angular.module = function () {
 6             modules = {};
 7             injector = new createInjector(modules);
 8             return {
 9                 injector: injector,
10                 factory: function (name, fn) {
11                     modules[name.trim()] = this.injector.invoke(fn); 
12                     return this;
13                 }
14             }
15         };

3. 執行時注入

最後經過 fn.apply方法把執行上下文,和依賴列表傳入函數並執行:npm

createInjector.prototype = {
            invoke: function (fn, self) {
                argsString = extractArgs(fn);
                args = [];
                argsString.forEach(function (val) {
                    args.push(this.cache[val.trim()]);
                }, this);
                return fn.apply(self, args);
            }
        };

簡化的所有代碼和執行效果見(可能需FQ):http://plnkr.co/edit/sJiIbzEXiqLLoQPeXBnR?p=preview
或查看源碼api

這裏是簡化的版本,實際angular的實現考慮了不少問題,如模塊管理,延遲執行等安全

angular 的實現

爲了簡單,咱們也按這三步來介紹angular DIapp

  1. 獲得模塊的依賴項
  2. 查找依賴項所對應的對象
  3. 執行時注入

注:如下代碼行數有就可能變

1. 獲得模塊的依賴項

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L81

var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

function extractArgs(fn) {
  var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
      args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
  return args;
}

2. 查找依賴項所對應的對象

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L807

 1     function getService(serviceName, caller) {
 2       if (cache.hasOwnProperty(serviceName)) {
 3         if (cache[serviceName] === INSTANTIATING) {
 4           throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
 5                     serviceName + ' <- ' + path.join(' <- '));
 6         }
 7         return cache[serviceName];
 8       } else {
 9         try {
10           path.unshift(serviceName);
11           cache[serviceName] = INSTANTIATING;
12           return cache[serviceName] = factory(serviceName, caller);
13         } catch (err) {
14           if (cache[serviceName] === INSTANTIATING) {
15             delete cache[serviceName];
16           }
17           throw err;
18         } finally {
19           path.shift();
20         }
21       }
22     }

3. 執行時注入

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L831

獲得參數:

 1   function injectionArgs(fn, locals, serviceName) {
 2       var args = [],
 3           $inject = createInjector.$$annotate(fn, strictDi, serviceName);
 4 
 5       for (var i = 0, length = $inject.length; i < length; i++) {
 6         var key = $inject[i];
 7         if (typeof key !== 'string') {
 8           throw $injectorMinErr('itkn',
 9                   'Incorrect injection token! Expected service name as string, got {0}', key);
10         }
11         args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
12                                                          getService(key, serviceName));
13       }
14       return args;
15     }

調用

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L861

 1    function invoke(fn, self, locals, serviceName) {
 2       if (typeof locals === 'string') {
 3         serviceName = locals;
 4         locals = null;
 5       }
 6 
 7       var args = injectionArgs(fn, locals, serviceName);
 8       if (isArray(fn)) {
 9         fn = fn[fn.length - 1];
10       }
11 
12       if (!isClass(fn)) {
13         // http://jsperf.com/angularjs-invoke-apply-vs-switch
14         // #5388
15         return fn.apply(self, args);
16       } else {
17         args.unshift(null);
18         return new (Function.prototype.bind.apply(fn, args))();
19       }
20     }

angular模塊管理,深坑

angular在每次應用啓動時,初始化一個Injector實例:

https://github.com/angular/angular.js/blob/master/src/Angular.js#L1685

var injector = createInjector(modules, config.strictDi);

由此代碼能夠看出對每個Angular應用來講,不管是哪一個模塊,全部的"provider"都是存在相同的providerCache或cache中

因此會致使一個被譽爲angular模塊管理的坑王的問題:
module 並無什麼命名空間的做用,當依賴名相同的時候,後面引用的會覆蓋前面引用的模塊。

具體的示例能夠查看:

http://plnkr.co/edit/TZ7hpMwuxk0surlcWDvU?p=preview

注:angular di用本文的調用方式壓縮代碼會出問題:能夠用g-annotate轉爲安全的調用方式。

到此angular di的實現原理已完成簡單的介紹,angular用了項目中幾乎不會用到的api:Function.prototype.toString 實現依賴注入,思路比較簡單,但實際框架中考慮的問題較多,更加詳細的實現能夠直接看angular的源碼

之後會逐步介紹angular其它原理。

轉載時請註明源出處: http://www.cnblogs.com/etoah/p/5460441.html

相關文章
相關標籤/搜索