我的angularjs源码学习之旅2——依赖注入

时间:2022-11-10 22:17:57

  依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题。简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时候再参数中添加这些依赖对象。

  理解很简单,我们以一个例子说明

var $name = "chua",$age = 26;
function myInfo($name,$age){
alert($name + ":" + $age );
}; //普通情况下应该执行
myInfo($name,$age);//"chua:26"
//实现依赖注入以后
myInfo();//本人希望打印的结果是"chua:26",但是毫无疑问在没有实现依赖注入之前是不好使的

那么怎么样实现依赖注入呢?


  首先,需要有一个函数来接管myInfo的函数定义,不然我们没法拿到myInfo的依赖对象名称加以保存。

function injector(fn){
//处理fn,保存依赖对象
...
return function(){
fn.apply({},xxx);//xxx是处理过后的参数
}
} //myInfo的定义变成
var myInfo = injector(function ($name,$age){
alert($name + ":" + $age );
});
myInfo();

  injector函数是怎么获取到myInfo的依赖对象$name、$age的?我们可以通过传入参数的函数fn.toString()来看,

//fn.toString()的结果为下面的字符串
"function myInfo($name,$age){
alert($name + ":" + $age );
}"

  参考angular的处理:从字符串中读取函数myInfo的参数还是能读取。

  STRIP_COMMENTS =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,
FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m,
FN_ARG =/^\s*(\S+?)\s*$/;
function injector(fn){
//处理fn,保存依赖对象
var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
var args = argDecl[1].split(",");//["$name","$age"]
for(var i = 0; i < args.length; i++){
args[i] = args[i].replace(FN_ARG,"$1");
}
...
return function(){
fn.apply({},xxx);//xxx是处理过后的参数
}
}

  但是有这个还不够,我们虽然拿到了要依赖的对象名称,但是我们并没有给这些名称指定对应的对象。所以应当有一个给这些要依赖的对象注册的过程。

  function injector(fn){
...
var args = argDecl[1].split(",");
for(var i = 0; i < args.length; i++){
args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
}
return function(){
fn.apply({},args);
}
}
injector.cache = {};
injector.register = function(key,resource){
injector.cache[key] = resource;
}
//先注册要依赖的对象
injector.register("$name",$name);
injector.register("$age",$age);

  这个时候我们可以使用injector来声明函数了

  var myInfo = injector(function($name,$age){
alert($name + ":" + $age );
});
//没有任何参数,得到期望的结果
myInfo();//chua:26

  只要是已经注册过的对象就多可以注入到使用injector函数来声明的函数中使用。完整的测试代码如下

<script>
var $name = "chua",
$age = 26,
STRIP_COMMENTS =/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm,
FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m,
FN_ARG =/^\s*(\S+?)\s*$/;
function injector(fn){
//处理fn,保存依赖对象
var fnText = fn.toString().replace(STRIP_COMMENTS, '');//"function myInfo($name,$age){ alert($name + ":" + $age ); }"
var argDecl = fnText.match(FN_ARGS);//["function myInfo($name,$age)", "$name,$age"]
var args = argDecl[1].split(",");//["$name","$age"]
for(var i = 0; i < args.length; i++){
args[i] = injector.cache[args[i].replace(FN_ARG,"$1")];
}
return function(){
fn.apply({},args);
}
}
injector.cache = {};
injector.register = function(key,resource){
injector.cache[key] = resource;
}
//先注册要依赖的对象
injector.register("$name",$name);
injector.register("$age",$age); //下面这个奇迹出现的时刻
var myInfo = injector(function($name,$age){
alert($name + ":" + $age );
});
myInfo();//chua:26
</script>

  

  好了简单的实现了一个依赖注入。但是依赖注入有一个问题,依赖注入是基于依赖对象的名称字符串的

injector(function($name,$age){...});

  那么上面的代码通过一般的压缩工具压缩以后会变成

a(function(b,c){...});

  已经无法找到依赖对象是"$name"/"$age"对应的对象了。

  

  所以angular想了几个办法。 

  angular简单注入方式为

myModule.controller('myCtrl',function($scope) {... });

  数组注释法

 myModule.controller('myCtrl', ['$scope', function($scope) {... }]);

  显式注入法

function myCtrl(){...};
myCtrl.$inject = ["$scope"];
myModule.controller('myCtrl',myCtrl);

  有一个第三方的插件ng-min,它可以帮你将以简单方式注入的代码自动转换成数组注释的方式,即能满足你写简洁代码的愿望,又能避免压缩出错问题。ng-min地址:https://github.com/btford/ngmin

  

angular实现依赖注入的方式


  应该说,js的依赖注入实现方式都是类似的。angular实现的依赖注入对象结构如下

injector = {
annotate: function annotate(fn, strictDi, name),
get: function getService(serviceName, caller),
has: function(name),
instantiate: function instantiate(Type, locals, serviceName),
invoke: function invoke(fn, self, locals, serviceName)
}

1.需要有注入的对象(依赖的对象)保存的地方。

  不知道还记不记得我的angular源码学习之旅1中提到angularModule = setupModuleLoader(window)给angular添加了模块声明(添加模块)、加载(获取模块)的方法angular.module。而在创建注入对象的时候(createInjector函数中)有这么一段代码

forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  其中loadModules(modulesToLoad)就是去加载依赖模块,在loadModules函数中真正去加载模块的是这段代码

        if (isString(module)) {
moduleFn = angularModule(module);
...
}

  angularModule即angular.module。前面也分析过这些模块实际都保存在一个外部变量modules中

  我的angularjs源码学习之旅2——依赖注入

  所以通过angular.module方法就是通过模块名去modules上添加或取出其中的模块。

  

2.获取依赖注入列表(即要依赖的对象的名称)

  获取方式也是通过fn.toString()来获取的。angular的实现是injector.annotate,函数体为

function annotate(fn, strictDi, name) {
var $inject,
fnText,
argDecl,
last; if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
...
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
arg.replace(FN_ARG, function(all, underscore, name) {
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
...
} else {
...
}
return $inject;
}

  只是应为angular接受多种注入方式所以处理代码有多个分支,而且也只是保存了依赖对象名称,并没有返回执行函数,执行函数是统一的injector.invoke。

  先前我们自己的实现执行是直接调用的myInfo();angular的执行函数是injector.invoke,函数体如下

    function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
} var args = [],
$inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key; for (i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
...
//获取依赖对象
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
if (isArray(fn)) {
fn = fn[length];
} // http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388 返回执行结果
return fn.apply(self, args);
}

  好了angularjs的依赖注入实现分析到此结束。更详细的细节可以断点跟踪。

  如果觉得本文不错,请点击右下方【推荐】!

我的angularjs源码学习之旅2——依赖注入的更多相关文章

  1. 我的angularjs源码学习之旅1——初识angularjs

    angular诞生有好几年光景了,有Google公司的支持版本更新还是比较快,从一开始就是一个热门技术,但是本人近期才开始接触到.只能感慨自己学习起点有点晚了.只能是加倍努力赶上技术前线. 因为有分析 ...

  2. 我的angularjs源码学习之旅3——脏检测与数据双向绑定

    前言 为了后面描述方便,我们将保存模块的对象modules叫做模块缓存.我们跟踪的例子如下 <div ng-app="myApp" ng-controller='myCtrl ...

  3. 任务20:DI初始化的源码解读 &amp&semi; 任务21:依赖注入的使用

    20 我们来看一下asp.net core中依赖注入的源码 https://github.com/aspnet/AspNetCore/tree/master/src/Hosting 任务21:依赖注入 ...

  4. Vue&period;js 源码分析&lpar;八&rpar; 基础篇 依赖注入 provide&sol;inject组合详解

    先来看看官网的介绍: 简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱.这个就是这对选项要干的事情 provide和 ...

  5. &lbrack;spring源码&rsqb; 小白级别的源码解析IOC容器的依赖注入&lpar;三&rpar;

    上一篇介绍了ioc容器的初始化过程,主要完成了ioc容器建立beanDefinition数据映射.并没有看到ioc容器对bean依赖关系进行注入. 接口getbean就是出发依赖注入发生的地方.下面从 ...

  6. Tomcat源码学习

    Tomcat源码学习(一) 转自:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-26309 ...

  7. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  8. MVVM大比拼之AngularJS源码精析

    MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者: ...

  9. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

随机推荐

  1. PYTHON开发--面向对象基础二

    一.成员修饰符 共有成员 私有成员, __字段名 - 无法直接访问,只能间接访问 1.     私有成员 1.1  普通方法种的私有成员 class Foo: def __init__(self, n ...

  2. cookieless domain

    概述 什么是cookieless domain?虽然名字中带有cookie,其实完全可以不使用cookie.这只是一种将网页中静态的文本,图片等的域名和主域名相区别开的方法. 主域名难免会使用到coo ...

  3. Unity3d在Window上使用SAPI进行语音识别

    前言 在之前<Unity利用Sapi进行windows语音开发>中,本计划不准备继续做语音识别.因为在unity3d中已经提供了语音识别的相关方法,详见unity3d的官方文档:https ...

  4. &lbrack;DeeplearningAI笔记&rsqb;改善深层神经网络&lowbar;深度学习的实用层面1&period;9&lowbar;归一化normalization

    觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.9 归一化Normaliation 训练神经网络,其中一个加速训练的方法就是归一化输入(normalize inputs). 假设我们有一个 ...

  5. 从BAT这种公司平薪跳槽头条,是否值得?

    有一个朋友之前就职于阿里,之前交流关于跳槽的问题,具体是这样的:阿里工作3年,拿到了头条的offer.但是非常纠结要不要接的问题.于是几个朋友聚在了一起讨论了这个问题 而且最近好多读者也在参加面试,接 ...

  6. &lbrack;Swift&rsqb;LeetCode282&period; 给表达式添加运算符 &vert; Expression Add Operators

    Given a string that contains only digits 0-9 and a target value, return all possibilities to add bin ...

  7. 批处理for中字符串截取必须先把循环变量代替出来才行!!!

    @echo off & setlocal enabledelayedexpansion set ifo=abc,def,ghi,jkl,mnopqrstuvwxyz0123456789 ech ...

  8. Vue调用百度接口做百度搜索

    这两天由于学习需要,需要用vue来调用api接口,但是以前没怎么接触过用vue来调用接口,不会没关系,发挥我们强大的学习能力,都不是事,学习了半天基本也就可以初级上手了,写篇随笔记录下来,方便以后回顾 ...

  9. Python之 ---成员修饰符

    一:成员修饰符:分为共有成员和私有成员: 私有成员:__通过两个下滑线:无法直接访问,要访问只能间接访问: 如下我们定义了一个对象,里面有两个共有的成员变量,成员变量是共有的时候我们可以外部访问,如果 ...

  10. MAC 下安装RabbitMQ

    1.使用brew来安装 RabbitMQ(地址:http://www.rabbitmq.com/install-standalone-mac.html ) 2.安装目录 /usr/local/Cell ...