四、反射方法
根据维基百科的定义反射是指一个程序在运行时检查和修改一个对象的结构和行为的能力。简单的说,在JavaScript的上下文里,这具体指读取和分析的对象或函数的源代码。让我们完成文章开头提到的doSomething函数。如果你在控制台输出doSomething.tostring()。你将得到如下的字符串:
复制代码 代码如下:
"function (service, router, other) {
var s = service();
var r = router();
}"
通过此方法返回的字符串给我们遍历参数的能力,更重要的是,能够获取他们的名字。这其实是Angular 实现它的依赖注入的方法。我偷了一点懒,直接截取Angular代码中获取参数的正则表达式。
复制代码 代码如下:
/^function\s*[^\(]*\(\s*([^\)]*)\)/m
我们可以像下面这样修改resolve 的代码:
复制代码 代码如下:
resolve: function() {
var func, deps, scope, args = [], self = this;
func = arguments[0];
deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
scope = arguments[1] || {};
return function() {
var a = Array.prototype.slice.call(arguments, 0);
for(var i=0; i<deps.length; i++) {
var d = deps[i];
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
}
func.apply(scope || {}, args);
}
}
我们执行正则表达式的结果如下:
复制代码 代码如下:
["function (service, router, other)", "service, router, other"]
看起来,我们只需要第二项。一旦我们清楚空格并分割字符串就得到deps数组。只有一个大的改变:
复制代码 代码如下:
var a = Array.prototype.slice.call(arguments, 0);
...
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
我们循环遍历dependencies数组,如果发现缺失项则尝试从arguments对象中获取。谢天谢地,当数组为空时,shift方法只是返回undefined,而不是抛出一个错误(这得益于web的思想)。新版的injector 能像下面这样使用:
复制代码 代码如下:
var doSomething = injector.resolve(function(service, other, router) {
expect(service().name).to.be('Service');
expect(router().name).to.be('Router');
expect(other).to.be('Other');
});
doSomething("Other");
不必重写依赖并且他们的顺序可以打乱。它仍然有效,我们成功复制了Angular的魔法。
然而,这种做法并不完美,这就是反射类型注射一个非常大的问题。压缩会破坏我们的逻辑,因为它改变参数的名字,我们将无法保持正确的映射关系。例如,doSometing()压缩后可能看起来像这样:
复制代码 代码如下:
var doSomething=function(e,t,n){var r=e();var i=t()}
Angular团队提出的解决方案看起来像:
var doSomething = injector.resolve(['service', 'router', function(service, router) {
}]);
这看起来很像我们开始时的解决方案。我没能找到一个更好的解决方案,所以决定结合这两种方法。下面是injector的最终版本。
复制代码 代码如下:
var injector = {
dependencies: {},
register: function(key, value) {
this.dependencies[key] = value;
},
resolve: function() {
var func, deps, scope, args = [], self = this;
if(typeof arguments[0] === 'string') {
func = arguments[1];
deps = arguments[0].replace(/ /g, '').split(',');
scope = arguments[2] || {};
} else {
func = arguments[0];
deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
scope = arguments[1] || {};
}
return function() {
var a = Array.prototype.slice.call(arguments, 0);
for(var i=0; i<deps.length; i++) {
var d = deps[i];
args.push(self.dependencies[d] && d != '' ? self.dependencies[d] : a.shift());
}
func.apply(scope || {}, args);
}
}
}
resolve访客接受两或三个参数,如果有两个参数它实际上和文章前面写的一样。然而,如果有三个参数,它会将第一个参数转换并填充deps数组,下面是一个测试例子:
复制代码 代码如下: