js依赖注入

相信用过AngularJS框架的jser应该都知道依赖注入的概念。依赖注入(Dependency Injection)简称DI,做过后台的程序员应该都不会陌生,因为很多后端语言,诸如C#、JAVA都有很好的框架支持,如JAVA中的Spring和C#中的Autofac。
关于控制反转(IOC)、依赖注入(DI),博客园有一篇好文依赖注入那些事儿

分析

依赖注入本质用途就是解决项目中的硬编码,降低代码耦合度,有效的管理模块依赖。RequireJS和AngularJS都有依赖注入的解决方案,AngularJS有一个内在的依赖注入机制,使用AngularJS,你可以把你的App分成许多个可以重复使用的组件,当需要这些组件的时候,可以通过依赖注入把这些组件注入到你的App中去。
RequireJS

1
2
3
define(['module1','module2'], function(module1, module2) {
// ...
});

AngularJS

1
2
3
4
5
6
angular.module('gulpAngular',
[
'ngAnimate',
'ngCookies',
'ngTouch'
]);

使用依赖注入我们应该要达到以下目标
1、我们应该能够注册依赖关系
2、注入应该接受一个函数,并返回一个我们需要的函数
3、我们不能写太多东西——我们需要精简漂亮的语法
4、注入应该保持被传递函数的作用域
5、被传递的函数应该能够接受自定义参数,而不仅仅是依赖描述
6、堪称完美的清单,下面 让我们实现它。

代码实现

首先,定义一个对象叫做injector,

1
2
3
4
5
6
7
8
9
10
var injector = {
dependencies: {},
register: function(name, src) {
this.dependencies[name] = src;
},
resolve: function(target) {

}
};
`

injector对象中有两个方法,regist负责注册需要依赖的模块并存在dependencies中,第二个方法是resolve,这个方法接收的参数是我们我们要注入依赖的对象,这里的关键的点就是这个注入器不应该调用我们的函数,所以我们在resolve方法中返回一个闭包来包裹我们的target,然后再调用它:

1
2
3
4
5
resolve: function(target) {
return function() {
target();
};
}

下一步到AngularJS源码中找到annotate方法,此方法将目标函数转换为一个字符串,删除它的注释(如果有),然后提取他的参数(依赖项):

1
2
3
4
5
6
7
8
9
10
11
12
13
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
fnText = target.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS)[1].split(/, ?/g);
var args = [];
for (var i = 0; i < argDecl.length; i++) {
if (this.dependencies[argDecl[i]]) {
args.push(this.dependencies[argDecl[i]]);
}
}
return function () {
target.apply({}, args);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var injector = {
//依赖库
dependencies: {},
//注册依赖
regist: function (name, src) {
this.dependencies[name] = src;
},
/**
* 返回闭包包裹要调用的函数
* @param target 需要注入的对象
* @returns {Function}
*/
resolve: function (target) {
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
fnText = target.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS)[1].split(/, ?/g);
var args = [];
for (var i = 0; i < argDecl.length; i++) {
if (this.dependencies[argDecl[i]]) {
args.push(this.dependencies[argDecl[i]]);
}
}
return function () {
target.apply({}, args);
}
}
};