AngularJS之依赖注入模拟实现


Posted in Javascript onAugust 19, 2016

一、概述 

AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的。 

依赖注入,简而言之,就是解除硬编码,达到解偶的目的。 

下面,我们看看AngularJS中常用的实现方式。 

方法一:推断式注入声明,假定参数名称就是依赖的名称。因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例。 

如下: 

//方法一:推断式注入声明,假定参数名称就是依赖的名称。
//因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,
//然后通过$injector将这些参数注入进对象实例
injector.invoke(function($http, $timeout){
 //TODO
});

方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。
 如下:

//方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,
//数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。
module.controller('name', ['$http', '$timeout', function($http, $timeout){
 //TODO
}]);

看了上述代码,心中有一疑问,这些是怎么实现的呢? 

哈哈,下面,我们就来一起模拟一下这些依赖注入方式,从而了解它们。 

二、搭建基本骨架 

依赖注入的获取过程就是,通过字段映射,从而获取到相应的方法。 

故而,要实现一个基本的依赖注入,我们需要一个存储空间(dependencies),存储所需键值(key/value);一个注册方法(register),用于新增键值对到存储空间中;还有一个就是核心实现方法(resolve),通过相关参数,到存储空间中获得对应的映射结果。 

So,基本骨架如下: 

var injector = {
 dependencies: {},
 register: function(key, value){
 this.dependencies[key] = value;
 return this;
 },
 resolve: function(){
    
 }
};

三、完善核心方法resolve 

从我们搭建的基本骨架中,可以发现,重点其实resolve方法,用于实现我们具体形式的依赖注入需求。 

首先,我们来实现,如下形式的依赖注入:推断式注入声明。 

如下: 

injector.resolve(function(Monkey, Dorie){
 Monkey();
 Dorie();
});

要实现上述效果,我们可以利用函数的toString()方法,我们可以将函数转换成字符串,从而通过正则表达式获得参数名,即key值。 然后通过key值,在存储空间dependencies找value值,没找到对应值,则报错。 

实现如下: 

var injector = {
 dependencies: {},
 register: function(key, value){
 this.dependencies[key] = value;
 return this;
 },
 resolve: function(){
 var func, deps, args = [], scope = null;
 func = arguments[0];
 //获取函数的参数名
 deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
 scope = arguments[1] || {};
 for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
  if(this.dependencies[d]){
  args.push(this.dependencies[d]);
  }else{
  throw new Error('Can\'t find ' + d);
  }
 }
 func.apply(scope, args);   
 }
};

测试代码,如下: 

<!DOCTYPE html>
 <head>
 <meta charset="utf-8"/>
 </head>
 <body>
 <script>
  var injector = {
  dependencies: {},
  register: function(key, value){
   this.dependencies[key] = value;
   return this;
  },
  resolve: function(){
   var func, deps, args = [], scope = null;
   func = arguments[0];
   //获取函数的参数名
   deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
   scope = arguments[1] || {};
   for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
   if(this.dependencies[d]){
    args.push(this.dependencies[d]);
   }else{
    throw new Error('Can\'t find ' + d);
   }
   }
   func.apply(scope, args);   
  }
  };
  //测试代码
  injector.register('Monkey', function(){
  console.log('Monkey');
  }).register('Dorie', function(){
  console.log('Dorie');
  });
  injector.resolve(function(Monkey, Dorie){
  Monkey();
  Dorie();
  console.log('-.-');
  }); 
 </script>
 </body>
</html>

推断式注入声明,有个缺点,就是不能利用压缩工具压缩,因为我们是通过函数的参数作为依赖的,当我们压缩时,会将参数名改掉,参数名都变了,那肯定扑街咯。 

那么下面,我们就看看行内注入声明,它可以弥补这缺点。 

实现行内注入声明,如下:

injector.resolve(['Monkey', 'Dorie', function(M, D){
 M();
 D();
}]);

利用typeof判断arguments[0]的类型可以辨别并获得依赖参数和函数。 

实现如下: 

var injector = {
 dependencies: {},
 register: function(key, value){
 this.dependencies[key] = value;
 return this;
 },
 resolve: function(){
 var firstParams, func, deps = [], scope = null, args = [];
 firstParams = arguments[0];
 scope = arguments[1] || {};
 //获得依赖参数
 for(var i = 0, len = firstParams.length; i < len; i++){
  var val = firstParams[i],
  type = typeof val;
  if(type === 'string'){
  deps.push(val);
  }else if(type === 'function'){
  func = val;
  }
 }
 //通过依赖参数,找到关联值
 for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
  if(this.dependencies[d]){
  args.push(this.dependencies[d]);
  }else{
  throw new Error('Can\'t find ' + d);
  }
 }
 func.apply(scope || {}, args);   
 }
};

测试代码,如下: 

<!DOCTYPE html>
 <head>
 <meta charset="utf-8"/>
 </head>
 <body>
 <script>
  var injector = {
  dependencies: {},
  register: function(key, value){
   this.dependencies[key] = value;
   return this;
  },
  resolve: function(){
   var firstParams, func, deps = [], scope = null, args = [];
   firstParams = arguments[0];
   scope = arguments[1] || {};
   //获得依赖参数
   for(var i = 0, len = firstParams.length; i < len; i++){
   var val = firstParams[i],
    type = typeof val;
   if(type === 'string'){
    deps.push(val);
   }else if(type === 'function'){
    func = val;
   }
   }
   //通过依赖参数,找到关联值
   for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
   if(this.dependencies[d]){
    args.push(this.dependencies[d]);
   }else{
    throw new Error('Can\'t find ' + d);
   }
   }
   func.apply(scope || {}, args);   
  }
  };
  //测试代码
  injector.register('Monkey', function(){
  console.log('Monkey');
  }).register('Dorie', function(){
  console.log('Dorie');
  });
  injector.resolve(['Monkey','Dorie',function(M, D){
  M();
  D();
  console.log('-.-');
  }]); 
 </script>
 </body>
</html>

因为行内注入声明,是通过字符串的形式作为依赖参数,so,压缩也不怕咯。
 最后,我们将上面实现的两种方法,整合到一起,就可以为所欲为啦。
 那,就合并下吧,如下: 

var injector = {
 dependencies: {},
 register: function(key, value){
 this.dependencies[key] = value;
 return this;
 },
 resolve: function(){
 var firstParams, func, deps = [], scope = null, args = [];
 firstParams = arguments[0];
 scope = arguments[1] || {};
 //判断哪种形式的注入
 if(typeof firstParams === 'function'){
  func = firstParams;
  deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
 }else{
  for(var i = 0, len = firstParams.length; i < len; i++){
  var val = firstParams[i],
   type = typeof val;
  if(type === 'string'){
   deps.push(val);
  }else if(type === 'function'){
   func = val;
  }
  } 
 }
 //通过依赖参数,找到关联值
 for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
  if(this.dependencies[d]){
  args.push(this.dependencies[d]);
  }else{
  throw new Error('Can\'t find ' + d);
  }
 }
 func.apply(scope || {}, args);   
 }
};

四、花絮—RequireJS之依赖注入 

依赖注入并非在AngularJS中有,倘若你使用过RequireJS,那么下面这种形式,不会陌生吧:

require(['Monkey', 'Dorie'], function(M, D){
 //TODO 
});

通过,上面我们一步步的模拟AngularJS依赖注入的实现,想必,看到这,你自己也会豁然开朗,换汤不换药嘛。 

模拟实现如下: 

var injector = {
 dependencies: {},
 register: function(key, value){
 this.dependencies[key] = value;
 return this;
 },
 resolve: function(deps, func, scope){
 var args = [];
 for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
  if(this.dependencies[d]){
  args.push(this.dependencies[d]);
  }else{
  throw new Error('Can\'t resolve ' + d);
  }
 }
 func.apply(scope || {}, args);
 }
};

测试代码如下: 

<!DOCTYPE html>
 <head>
 <meta charset="utf-8"/>
 </head>
 <body>
 <script>
  var injector = {
  dependencies: {},
  register: function(key, value){
   this.dependencies[key] = value;
   return this;
  },
  resolve: function(deps, func, scope){
   var args = [];
   for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
   if(this.dependencies[d]){
    args.push(this.dependencies[d]);
   }else{
    throw new Error('Can\'t resolve ' + d);
   }
   }
   func.apply(scope || {}, args);
  }
  };
  //测试代码  
  injector.register('Monkey', function(){
  console.log('Monkey');
  }).register('Dorie', function(){
  console.log('Dorie');
  });
  injector.resolve(['Monkey', 'Dorie'], function(M, D){
  M();
  D();
  console.log('-.-');
  }); 
 </script>
 </body>
</html>

五、参考 

1、AngularJS应用开发思维之3:依赖注入 
2、Dependency injection in JavaScript

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript 学习笔记(六)
Dec 31 Javascript
js或者jquery判断图片是否加载完成实现代码
Mar 20 Javascript
JavaScript获取两个数组交集的方法
Jun 09 Javascript
javascript中return,return true,return false三者的用法及区别
Nov 17 Javascript
全面了解JavaScript对象进阶
Jul 19 Javascript
jQuery EasyUI 右键菜单--关闭标签/选项卡的简单实例
Oct 10 Javascript
html+javascript+bootstrap实现层级多选框全层全选和多选功能
Mar 09 Javascript
JS库 Highlightjs 添加代码行号的实现代码
Sep 13 Javascript
js使用xml数据载体实现城市省份二级联动效果
Nov 08 Javascript
说说Vue.js中的functional函数化组件的使用
Feb 12 Javascript
JavaScript 装逼指南(js另类写法)
May 10 Javascript
Electron整合React使用搭建开发环境的步骤详解
Jun 07 Javascript
AngularJS入门教程之XHR和依赖注入详解
Aug 18 #Javascript
JavaScript中函数声明与函数表达式的区别详解
Aug 18 #Javascript
Javascript中apply、call、bind的巧妙使用
Aug 18 #Javascript
AngularJS入门教程之双向绑定详解
Aug 18 #Javascript
AngularJS入门教程之迭代器过滤详解
Aug 18 #Javascript
AngularJS入门教程之AngularJS 模板
Aug 18 #Javascript
AngularJS入门教程之静态模板详解
Aug 18 #Javascript
You might like
十天学会php之第二天
2006/10/09 PHP
php auth_http类库进行身份效验
2009/03/19 PHP
Zend Studio (eclipse)使用速度优化方法
2011/03/23 PHP
PHP+Mysql日期时间如何转换(UNIX时间戳和格式化日期)
2012/07/15 PHP
PHP实现C#山寨ArrayList的方法
2015/07/16 PHP
使用Composer安装Yii框架的方法
2016/03/15 PHP
Thinkphp5框架实现图片、音频和视频文件的上传功能详解
2019/08/27 PHP
JS 控制CSS样式表
2009/08/20 Javascript
javascript event 事件解析
2011/01/31 Javascript
解读JavaScript中 For, While与递归的用法
2013/05/07 Javascript
鼠标滚轴控制文本框值的JS代码
2013/11/19 Javascript
JavaScript插件化开发教程 (三)
2015/01/27 Javascript
玩转NODE.JS(四)-搭建简单的聊天室的代码
2016/11/11 Javascript
JQuery学习总结【二】
2016/12/01 Javascript
基于VUE选择上传图片并页面显示(图片可删除)
2017/05/25 Javascript
加载 vue 远程代码的组件实例详解
2017/11/20 Javascript
让bootstrap的carousel支持滑动滚屏的实现代码
2017/11/27 Javascript
layer.js open 隐藏滚动条的例子
2019/09/05 Javascript
vue-cli3 取消eslint校验代码的解决办法
2020/01/16 Javascript
[02:27]2018DOTA2亚洲邀请赛赛前采访-OpTic
2018/04/03 DOTA
Python三元运算实现方法
2015/01/12 Python
Django中reverse反转并且传递参数的方法
2019/08/06 Python
Python上下文管理器用法及实例解析
2019/11/11 Python
详解Django配置优化方法
2019/11/18 Python
python 输出列表元素实例(以空格/逗号为分隔符)
2019/12/25 Python
Python是什么 Python的用处
2020/05/26 Python
在python中list作函数形参,防止被实参修改的实现方法
2020/06/05 Python
python 生成器需注意的小问题
2020/09/29 Python
俄罗斯电子产品、计算机和家用电器购物网站:OLDI
2019/10/27 全球购物
C#里面可以避免一个类被其他类继承么?如何?
2013/09/26 面试题
酒店销售经理岗位职责
2014/01/31 职场文书
详细的本科生职业生涯规划范文
2014/09/16 职场文书
先进学校事迹材料
2014/12/30 职场文书
如何将numpy二维数组中的np.nan值替换为指定的值
2021/05/14 Python
MySQL实例精讲单行函数以及字符数学日期流程控制
2021/10/15 MySQL
小程序自定义轮播图圆点组件
2022/06/25 Javascript