依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多,

angular官方的文档,也有很详细的说明。但介绍原理的较少,angular代码结构较复杂,文章实现了一简化版本的DI,核心代码只有30行左右,相看实现效果(可能需翻墙)或查看源码

这篇文章用尽量简单的方式说一说 angular依赖注入的实现。

简化的实现原理

要实现注入,基本有三步:

  1. 得到模块的依赖项
  2. 查找依赖项所对应的对象
  3. 执行时注入

1. 得到模块的依赖项

javascript 实现DI的核心api是Function.prototype.toString,对一个函数执行toString,它会返回函数的源码字符串,这样我们就可以通过正则匹配的方式拿到这个函数的参数列表:

function extractArgs(fn) { //angular 这里还加了注释、箭头函数的处理
var args = fn.toString().match(/^[^\(]*\(\s*([^\)]*)\)/m);
return args[1].split(',');
}

2. 查找依赖项所对应的对象

java与.net通过反射来获取依赖对象,js是动态语言,直接一个object[name]就可以直接拿到对象。所以只要用一个对象保存对象或函数列表就可以了

function createInjector(cache) {
this.cache = cache; }
angular.module = function () {
modules = {};
injector = new createInjector(modules);
return {
injector: injector,
factory: function (name, fn) {
modules[name.trim()] = this.injector.invoke(fn);
return this;
}
}
};

3. 执行时注入

最后通过 fn.apply方法把执行上下文,和依赖列表传入函数并执行:


createInjector.prototype = {
invoke: function (fn, self) {
argsString = extractArgs(fn);
args = [];
argsString.forEach(function (val) {
args.push(this.cache[val.trim()]);
}, this);
return fn.apply(self, args);
}
};

简化的全部代码和执行效果见(可能需翻墙):http://plnkr.co/edit/sJiIbzEXiqLLoQPeXBnR?p=preview

或查看源码

这里是简化的版本,实际angular的实现考虑了很多问题,如模块管理,延迟执行等

angular 的实现

为了简单,我们也按这三步来介绍angular DI

  1. 得到模块的依赖项
  2. 查找依赖项所对应的对象
  3. 执行时注入

注:以下代码行数有就可能变

1. 得到模块的依赖项

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L81

var ARROW_ARG = /^([^\(]+?)=>/;
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; function extractArgs(fn) {
var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
args = fnText.match(ARROW_ARG) || fnText.match(FN_ARGS);
return args;
}

2. 查找依赖项所对应的对象

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L807

    function getService(serviceName, caller) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
path.shift();
}
}
}

3. 执行时注入

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L831

得到参数:

    function injectionArgs(fn, locals, serviceName) {
var args = [],
$inject = createInjector.$$annotate(fn, strictDi, serviceName); for (var i = 0, length = $inject.length; i < length; i++) {
var key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
getService(key, serviceName));
}
return args;
}

调用

https://github.com/angular/angular.js/blob/master/src%2Fauto%2Finjector.js#L861

    function invoke(fn, self, locals, serviceName) {
if (typeof locals === 'string') {
serviceName = locals;
locals = null;
} var args = injectionArgs(fn, locals, serviceName);
if (isArray(fn)) {
fn = fn[fn.length - 1];
} if (!isClass(fn)) {
// http://jsperf.com/angularjs-invoke-apply-vs-switch
// #5388
return fn.apply(self, args);
} else {
args.unshift(null);
return new (Function.prototype.bind.apply(fn, args))();
}
}

angular模块管理,深坑

angular在每次应用启动时,初始化一个Injector实例:

https://github.com/angular/angular.js/blob/master/src/Angular.js#L1685

var injector = createInjector(modules, config.strictDi);

由此代码可以看出对每一个Angular应用来说,无论是哪个模块,所有的"provider"都是存在相同的providerCache或cache中

所以会导致一个被誉为angular模块管理的坑王的问题:

module 并没有什么命名空间的作用,当依赖名相同的时候,后面引用的会覆盖前面引用的模块。

具体的示例可以查看:

http://plnkr.co/edit/TZ7hpMwuxk0surlcWDvU?p=preview

注:angular di用本文的调用方式压缩代码会出问题:可以用g-annotate转为安全的调用方式。

到此angular di的实现原理已完成简单的介绍,angular用了项目中几乎不会用到的api:Function.prototype.toString 实现依赖注入,思路比较简单,但实际框架中考虑的问题较多,更加详细的实现可以直接看angular的源码

以后会逐步介绍angular其它原理。

转载时请注明源出处: http://www.cnblogs.com/etoah/p/5460441.html

30行代码让你理解angular依赖注入:angular 依赖注入原理的更多相关文章

  1. 30行代码搞定WCF并发性能测试

    [以下只是个人观点,欢迎交流] 30行代码搞定WCF并发性能 轻量级测试. 1. 调用并发测试接口 static void Main()         {               List< ...

  2. 10分钟教你用python 30行代码搞定简单手写识别!

    欲直接下载代码文件,关注我们的公众号哦!查看历史消息即可! 手写笔记还是电子笔记好呢? 毕业季刚结束,眼瞅着2018级小萌新马上就要来了,老腊肉小编为了咱学弟学妹们的学习,绞尽脑汁准备编一套大学秘籍, ...

  3. Tensorflow快餐教程(1) - 30行代码搞定手写识别

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/lusing/article/details ...

  4. 30行代码实现Javascript中的MVC

    从09年左右开始,MVC逐渐在前端领域大放异彩,并终于在刚刚过去的2015年随着React Native的推出而迎来大爆发:AngularJS.EmberJS.Backbone.ReactJS.Rio ...

  5. 30 行代码实现 JS 中的 MVC

    一连串的名字走马观花式的出现和更迭,它们中一些已经渐渐淡出了大家的视野,一些还在迅速茁壮成长,一些则已经在特定的生态环境中独当一面舍我其谁.但不论如何,MVC已经并将持续深刻地影响前端工程师们的思维方 ...

  6. 数据结构 | 30行代码,手把手带你实现Trie树

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第28篇文章,我们一起来聊聊一个经典的字符串处理数据结构--Trie. 在之前的4篇文章当中我们介绍了关于博弈论的 ...

  7. 30行代码消费腾讯人工智能开放平台提供的自然语言处理API

    腾讯人工智能AI开放平台上提供了很多免费的人工智能API,开发人员只需要一个QQ号就可以登录进去使用. 腾讯人工智能AI开放平台的地址:https://ai.qq.com/ 里面的好东西很多,以自然语 ...

  8. 算法数据结构 | 只要30行代码,实现快速匹配字符串的KMP算法

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法数据结构专题的第29篇文章,我们来聊一个新的字符串匹配算法--KMP. KMP这个名字不是视频播放器,更不是看毛片,它其实是由Kn ...

  9. 用python 30行代码,搞定一个简单截图调取的百度识字功能

    在做一个数据标注过程中人工需要识别文字. 想了想写了一个小脚本, 大致过程这样的. 截图功能写了好久也没写明白,索性直接调用第三方的截图工具了,在采用qq或者微信截图时,截图完成后保存大致保存在剪切板 ...

随机推荐

  1. JS简介,如何快熟JS。打下结实基础。

    JS决定网页的行为,有行为就有逻辑,而计算机只是人工智能,你给它什么样的指令,它就会按照你的指令去运行. 有人会问,既然是给出指令,那还需要什么逻辑? 这里我举一个简单的小例子来说明一下! 到你想输入 ...

  2. Javascript基础回顾 之(一) 类型

    本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者 ...

  3. TDDL分库分表规则

    规则如下: 判断一个ID在哪个库里的公式是:id % 4 / 2判断一个ID在哪个表里的公式是:id % 4 % 2 其中4表示总共有多少个分表,2表示总共有多少个数据库:上面这个例子,表示总共有2个 ...

  4. java中文乱码解决之道(四)-----java编码转换过程

    前面三篇博客侧重介绍字符.编码问题,通过这三篇博客各位博友对各种字符编码有了一个初步的了解,要了解java的中文问题这是必须要了解的.但是了解这些仅仅只是一个开始,以下博客将侧重介绍java乱码是如何 ...

  5. android内部培训视频_第二节 布局基础

    第二节:布局入门 一.线性布局 需要掌握的属性: 1.orientation:排列方式 vertical:垂直 Horizontal:水平 2.weight:水平布局的权重 3.gravity:子控件 ...

  6. sql 笔记(mysql)

    Windows 安装mysql(zip包) 1,zip包解压到要安装目录 2,配置环境变量,Path后加mysql路径\bin 3,修改配置文件,mysql目录下my-default.ini base ...

  7. weblogic配置数据源

    启动weblogic 管理服务器,使用管理用户登录weblogic管理控制台   打开管理控制台后,在左侧的树形域结构中,选择服务->数据源. 在右侧的窗口中,选择 新建->一般数据源   ...

  8. linux创建进程fork的方法步骤

    fork创建进程 函数原型如下 #include// 必须引入头文件,使用fork函数的时候,必须包含这个头文件,否则,系统找不到fork函数 pid_t fork(void); //void代表没有 ...

  9. SQL Server 堆表行存储大小(Record Size)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 堆表行记录存储格式(Heap) 案例分析(Case) 参考文献(References) 二.背 ...

  10. Sql Server系列:通用表表达式CTE

    1 CTE语法WITH关键字 通用表表达式(Common Table Express, CTE),将派生表定义在查询的最前面.要使用CTE开始创建一个查询,可以使用WITH关键字. CTE语法: WI ...