[ AngularJS ] 本身实现一个简单的依赖注入

咱们开始使用AngularJS的时候,它的双向数据绑定是最让咱们印象深入的,那第二个就应该算是它的那神奇的依赖注入的功能了。javascript

举个栗子

function myController = ($scope, $http){
    $http.get('users/users.json').success(function(data){
        $scope.users = data;
    })
}

这是一个典型的angularjs的控制器,他发送了一个http请求,从后台获取json数据,而后把他传递给当前做用域。你会发现,咱们并无执行这个myController函数(咱们没有机会给它传递参数),其实,是angular这个框架帮咱们作了;那么,$scope, $http这些变量从哪里来的呢? 这是angular的一个很是酷的特性,咱们一步步来实现一个简单的注入器,从而知道它是怎么实现的。html

传统的方式

如今,咱们有一个js函数去把用户列表展现在网页上,那么这个函数就须要从ajax获取过来的数据和用于展现数据的DOM元素。为了简单点,咱们直接用静态数据了来代替ajax的http请求:java

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是一个包含全部用户的数组。下面来使用:angularjs

var displayUsers = function(domEl, ajax) {
  ajax.get('/api/users', function(users) {
    var html = '';
    for(var i=0; i < users.length; i++) {
      html += '<p>' + users[i] + '</p>';
    }
    domEl.innerHTML = html;
  });
}

很明显,咱们运行displayUsers(body, ajaxFn),咱们将会看到三个用户名显示在网页上,同时控制台输出了/api/users requested。那这样的话,咱们能够认为咱们的displayUsers函数有两个依赖,bodyajaxFn.ajax

那么如今,咱们的目的是使displayUsers这个函数在不传参数的状况下照样工做,即,运行displayUsers()要获得和上面同样的结果,若是咱们直接运行,你会发现报错了:正则表达式

Uncaught TypeError: Cannot read property 'get' of undefined

很明显是由于ajax这个参数没有定义。json

来实现依赖注入

如今大部分的框架都提供依赖注入机制的模块,可能会叫作:injector。为了在某个地方使用一个依赖,咱们须要在那个地方注册那个依赖:api

来建立咱们的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();
  }
}

上面主要是经过正则表达式来找到依赖项,来看看结果:

clipboard.png

你会看到返回的数组中包含了咱们须要的依赖项,咱们找到它们并用注入器注册和存储:

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&lt;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 不一样,它传递的是真实的依赖名称。

译自:http://www.sitepoint.com/revealing-magic-javascript/

有错误还请指正!

相关文章
相关标签/搜索