依賴注入(Dependency Injection,簡稱DI)是像C#,java等典型的面嚮對象語言框架設計原則控制反轉的一種典型的一種實現方式,angular把它引入到js中,介紹angular依賴注入的使用方式的文章不少,
angular官方的文檔,也有很詳細的說明。但介紹原理的較少,angular代碼結構較複雜,文章實現了一簡化版本的DI,核心代碼只有30行左右,相看實現效果(可能需FQ)或查看源碼javascript
這篇文章用盡可能簡單的方式說一說 angular依賴注入的實現。html
要實現注入,基本有三步:java
javascript 實現DI的核心api是git
Function.prototype.toString
對一個函數執行toString,它會返回函數的源碼字符串,這樣咱們就能夠經過正則匹配的方式拿到這個函數的參數列表:angularjs
function extractArgs(fn) { //angular 這裏還加了註釋、箭頭函數的處理 var args = fn.toString().match(/^[^\(]*\(\s*([^\)]*)\)/m); return args[1].split(','); }
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 };
最後經過 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 DIapp
注:如下代碼行數有就可能變
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; }
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 }
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在每次應用啓動時,初始化一個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