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 相关文章推荐
我的Node.js学习之路(三)--node.js作用、回调、同步和异步代码 以及事件循环
Jul 06 Javascript
60个很实用的jQuery代码开发技巧收集
Dec 15 Javascript
Js获取图片原始宽高的实现代码
May 17 Javascript
js实现可控制左右方向的无缝滚动效果
May 29 Javascript
jQuery layui常用方法介绍
Jul 25 Javascript
浅谈JS继承_借用构造函数 &amp; 组合式继承
Aug 16 Javascript
javascript帧动画(实例讲解)
Sep 02 Javascript
原生JS+HTML5实现跟随鼠标一起流动的粒子动画效果
May 03 Javascript
微信小程序实现下拉菜单切换效果
Mar 30 Javascript
使用 Vue cli 3.0 构建自定义组件库的方法
Apr 30 Javascript
flexible.js实现移动端rem适配方案
Apr 07 Javascript
使用Vue+Django+Ant Design做一个留言评论模块的示例代码
Jun 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在apache环境下实现gzip配置方法
2015/04/02 PHP
php利用事务处理转账问题
2015/04/22 PHP
PHP图像处理类库及演示分享
2015/05/17 PHP
PHP数组实例详解
2016/06/26 PHP
php array_slice 取出数组中的一段序列实例
2016/11/04 PHP
推荐:极酷右键菜单
2006/11/29 Javascript
js 获取经纬度的实现方法
2016/06/20 Javascript
Angualrjs和bootstrap相结合实现数据表格table
2017/03/30 Javascript
jQuery 表单序列化实例代码
2017/06/11 jQuery
vuejs实现本地数据的筛选分页功能思路详解
2017/11/15 Javascript
webpack+vue2构建vue项目骨架的方法
2018/01/09 Javascript
Layui实现带查询条件的分页
2019/07/27 Javascript
Vue开发环境跨域访问问题
2020/01/22 Javascript
node 版本切换的实现
2020/02/02 Javascript
[03:40]DOTA2亚洲邀请赛小组赛第二日 赛事回顾
2015/01/31 DOTA
python基础教程之字典操作详解
2014/03/25 Python
详解Python3中的Sequence type的使用
2015/08/01 Python
matplotlib作图添加表格实例代码
2018/01/23 Python
Python抓取聚划算商品分析页面获取商品信息并以XML格式保存到本地
2018/02/23 Python
用python编写第一个IDA插件的实例
2018/05/29 Python
使用django实现一个代码发布系统
2019/07/18 Python
Python交互式图形编程的实现
2019/07/25 Python
应届大学生自荐信格式
2013/09/21 职场文书
超市促销活动方案
2014/03/05 职场文书
中式结婚主持词
2014/03/14 职场文书
经济信息系毕业生自荐信范文
2014/03/15 职场文书
求职者怎样写自荐信
2014/04/13 职场文书
技术合作协议书范本
2014/04/18 职场文书
2014年国庆节活动总结
2014/08/26 职场文书
项目委托协议书(最新)
2014/09/13 职场文书
漂亮妈妈观后感
2015/06/08 职场文书
学雷锋广播稿大全
2015/08/19 职场文书
Redis可视化客户端小结
2021/06/10 Redis
死磕 java同步系列之synchronized解析
2021/06/28 Java/Android
Spring Boot配合PageHelper优化大表查询数据分页
2022/04/20 Java/Android
HTML页面中使两个div并排显示的实现
2022/05/15 HTML / CSS