我们开始使用AngularJS的时候,它的双向数据绑定是最让我们印象深刻的,那第二个就应该算是它的那神奇的依赖注入的功能了。
举个栗子
function myController = ($scope, $http){ $http.get('users/users.json').success(function(data){ $scope.users = data; })}
这是一个典型的angularjs
的控制器,他发送了一个http请求,从后台获取json数据,然后把他传递给当前作用域。你会发现,我们并没有执行这个myController
函数(我们没有机会给它传递参数),其实,是angular这个框架帮我们做了;那么,$scope, $http
这些变量从哪里来的呢? 这是angular
的一个非常酷的特性,我们一步步来实现一个简单的注入器,从而知道它是怎么实现的。
传统的方式
现在,我们有一个js函数去把用户列表展示在网页上,那么这个函数就需要从ajax获取过来的数据和用于展示数据的DOM元素。为了简单点,我们直接用静态数据了来代替ajax的http请求:
var data = ['John', 'Steve', 'David'];var body = document.querySelector('body');var ajaxFn = { get: function(path, cb) { console.log(path + ' requested'); cb(data); }}
我们把body标签作为列表的容器,ajaxFn是模拟ajax请求的对象,data是一个包含所有用户的数组。下面来使用:
var displayUsers = function(domEl, ajax) { ajax.get('/api/users', function(users) { var html = ''; for(var i=0; i < users.length; i++) { html += '' + users[i] + '
'; } domEl.innerHTML = html; });}
很明显,我们运行displayUsers(body, ajaxFn)
,我们将会看到三个用户名显示在网页上,同时控制台输出了/api/users requested
。那这样的话,我们可以认为我们的displayUsers函数有两个依赖,body
和ajaxFn
.
那么现在,我们的目的是使displayUsers这个函数在不传参数的情况下照样工作,即,运行displayUsers()
要得到和上面一样的结果,如果我们直接运行,你会发现报错了:
Uncaught TypeError: Cannot read property 'get' of undefined
很明显是因为ajax这个参数没有定义。
来实现依赖注入
现在大部分的框架都提供依赖注入机制的模块,可能会叫做:injector
。为了在某个地方使用一个依赖,我们需要在那个地方注册那个依赖:
来创建我们的injector
:
var injector = { storage: {}, register: function(name, resource) { this.storage[name] = resource; }, resolve: function(target) { }};
我们只需要两个方法,第一个是register, 接收我们的依赖并将它存储起来。第二个方法是resolve,这个方法接收的参数是我们我们要注入依赖的对象,这里的关键的点就是这个注入器不应该调用我们的函数,所以我们在resolve方法中返回一个闭包来包裹我们的target,然后再调用它:
resolve: function(target) { return function() { target(); };}
那么到现在呢,我们的注入器依然是不可用的:
displayUsers = injector.resolve(displayUsers);displayUsers();
我们依然得到同样的错误,你觉得少了什么呢?绝壁就是依赖项了,所以我们下一步就是要找出这个函数的依赖项,如何找? 这是比较棘手的问题,不过我们可以参考angularjs的方式:
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;...function annotate(fn) { ... fnText = fn.toString().replace(STRIP_COMMENTS, ''); argDecl = fnText.match(FN_ARGS); ...}We purpo
这里省略了一些细节部分,我们需要注意的就是annotate
这个函数,它负责将目标函数转换为一个字符串,删除它的注释(如果有),然后提取他的参数(依赖项):
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); console.log(argDecl); return function() { target(); }}
上面主要是通过正则表达式来找到依赖项,来看看结果:
你会看到返回的数组中包含了我们需要的依赖项,我们找到它们并用注入器注册和存储:
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.storage[argDecl[i]]) { args.push(this.storage[argDecl[i]]); } } return function() { target.apply({}, args); }}
现在,我们可以使用我们的注入器了:
injector.register('domEl', body);injector.register('ajax', ajaxFn);displayUsers = injector.resolve(displayUsers);displayUsers();
你会发现得到了和刚开始一样的结果。
这样做有什么优点呢?有点就是我们可以把DOM元素和ajaxFn注入到任何一个函数中,我们甚至可以将我们的应用程序配置成这样的方式,那我们就不用通过继承来传递这些对象了。这仅仅是injector的register和resolve方法而已,我们的注入器还不够完美,还有扩展空间,比如定义作用域scope.
而angularjs中的依赖注入更加强大:
displayUsers = injector.resolve(['domEl', 'ajax', displayUsers]);
和displayUsers 不同,它传递的是真实的依赖名称。
译自:
有错误还请指正!