依赖注入(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. NLP常用工具

    1.统计类工具:可参见[统计学习常用Python扩展包] 2.linux自带工具:可参见[[整理]Linux常用文本处理命令] 3.简繁转换工具:opencc Open Chinese Convert ...

  2. SQL Server 2012大幅增强T-SQL

    SQL Server 2012对T-SQL进行了大幅增强,其中包括支持ANSI FIRST_VALUE和LAST_VALUE函数,支持使用FETCH与OFFSET进行声明式数据分页,以及支持.NET中 ...

  3. LVS DR模式 RealServer 为 Windows 2008 R2配置

    有3篇文档详细介绍 http://kb.linuxvirtualserver.org/wiki/Windows_Servers_in_LVS/DR_and_LVS/TUN_Clusters http: ...

  4. node基本理念(事件、多线程、进程)

    关于node的几点理解: 1.所谓的node单线程,是指node的event loop,也就是我们的业务脚本是跑在一个单线程中的,可以理解为主线程: 2.若存在IO操作,例如文件.网络,是单独在线程里 ...

  5. 【BOOM】一款有趣的Javascript动画效果

    实践出真知,有的时候看到一些有趣的现象就想着用自己所学的知识复现一下.    boomJS 缘起 前几天在 github 上看到同事的一个这样的小项目,在 IOS 上实现了这样一个小动画效果,看上去蛮 ...

  6. 1ms引发的问题

    最近在跟SQLServer数据库进行交互的时候发现一个奇怪的问题,在往数据库里边插入日期型数据的时候,在C#里面赋值的为 2014/05/19 23:59:59,但是存到数据库里边就变成了2014/0 ...

  7. 干货!表达式树解析"框架"(1)

    最新设计请移步 轻量级表达式树解析框架Faller http://www.cnblogs.com/blqw/p/Faller.html 关于我和表达式树 其实我也没有深入了解表达式树一些内在实现的原理 ...

  8. 三周,用长轮询实现Chat并迁移到Azure测试

    公司的OA从零开始进行开发,继简单的单点登陆.角色与权限.消息中间件之后,轮到在线即时通信的模块需要我独立去完成.这三周除了逛网店见爱*看动漫接兼职,基本上都花在这上面了.简单地说就是用MVC4基于长 ...

  9. 《Entity Framework 6 Recipes》翻译系列 (4) -----第二章 实体数据建模基础之从已存在的数据库创建模型

    不知道对EF感兴趣的并不多,还是我翻译有问题(如果是,恳请你指正),通过前几篇的反馈,阅读这个系列的人不多.不要这事到最后成了吃不讨好的事就麻烦了,废话就到这里,直奔主题. 2-2 从已存在的数据库创 ...

  10. jsdoc

    一.javascript注释规范 我们在编写javascript文件的时候,一般会添加一些注释.例如一些文件.类.方法和属性都应该用合适的标记和类型进行注释.这里不但方便我们的阅读,也能养成一个好的习 ...