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 必知必会之closure
Sep 21 Javascript
关于Aptana Studio生成自动备份文件的解决办法
Dec 23 Javascript
基于JQuery的浮动DIV显示提示信息并自动隐藏
Feb 11 Javascript
js防止页面被iframe调用的方法
Oct 30 Javascript
JavaScript更改字符串的大小写
May 07 Javascript
jquery判断复选框选中状态以及区分attr和prop
Dec 18 Javascript
深入理解js promise chain
May 05 Javascript
Bootstrap 网格系统布局详解
Mar 19 Javascript
微信小程序页面间通信的5种方式
Mar 31 Javascript
JavaScript 完成注册页面表单校验的实例
Aug 19 Javascript
基于Webpack4和React hooks搭建项目的方法
Feb 05 Javascript
简单两步使用node发送qq邮件的方法
Mar 01 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 google或baidu分页代码
2009/11/26 PHP
PHP中输出转义JavaScript代码的实现代码
2011/04/22 PHP
php和editplus正则表达式去除空白行
2015/04/17 PHP
php商品对比功能代码分享
2015/09/24 PHP
PHP中文竖排转换实现方法
2015/10/23 PHP
php面向对象程序设计中self与static的区别分析
2019/05/21 PHP
IE6/7 and IE8/9/10(IE7模式)依次隐藏具有absolute或relative的父元素和子元素后再显示父元素
2011/07/31 Javascript
jQuery使用之处理页面元素用法实例
2015/01/19 Javascript
基于gulp合并压缩Seajs模块的方式说明
2016/06/14 Javascript
JS获取随机数和时间转换的简单实例
2016/07/10 Javascript
AngularJS辅助库browserTrigger用法示例
2016/11/03 Javascript
Ionic + Angular.js实现图片轮播的方法示例
2017/05/21 Javascript
Vue的事件响应式进度条组件实例详解
2018/02/04 Javascript
JavaScript中的 new 命令
2019/05/22 Javascript
Vue 子组件与数据传递问题及注意事项
2019/07/11 Javascript
微信小程序实现下滑到底部自动翻页功能
2020/03/07 Javascript
解决antd 表单设置默认值initialValue后验证失效的问题
2020/11/02 Javascript
浅谈Vue开发人员的7个最好的VSCode扩展
2021/01/20 Vue.js
[52:52]完美世界DOTA2联赛PWL S3 LBZS vs access 第一场 12.10
2020/12/13 DOTA
python实现巡检系统(solaris)示例
2014/04/02 Python
在Python的Flask框架中构建Web表单的教程
2016/06/04 Python
基于python时间处理方法(详解)
2017/08/14 Python
VScode编写第一个Python程序HelloWorld步骤
2018/04/06 Python
面向初学者的Python编辑器Mu
2018/10/08 Python
python之当你发现QTimer不能用时的解决方法
2019/06/21 Python
对PyQt5的输入对话框使用(QInputDialog)详解
2019/06/25 Python
HTML5的表单(绝对特别强大的功能)使用示例
2013/06/20 HTML / CSS
德国Discount-Apotheke中文官网:DC德式康线上药房
2020/02/18 全球购物
通往英国高街的商店橱窗:Down Your High Street
2020/07/19 全球购物
生物科学系大学生的自我评价
2013/12/20 职场文书
大学军训感言200字
2014/02/26 职场文书
以幸福为主题的活动方案
2014/08/22 职场文书
2014乡镇党委副书记对照检查材料思想汇报
2014/10/09 职场文书
2015年英语教师工作总结
2015/05/20 职场文书
HTML中table表格拆分合并(colspan、rowspan)
2021/04/07 HTML / CSS
node.js如何自定义实现一个EventEmitter
2021/07/16 Javascript