依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖注入的使用方式的文章很多,
angular官方的文档,也有很详细的说明。但介绍原理的较少,angular代码结构较复杂,文章实现了一简化版本的DI,核心代码只有30行左右,相看实现效果(可能需FQ)或查看源码

这篇文章用尽量简单的方式说一说 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);
}
};

简化的全部代码和执行效果见(可能需FQ):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

angular 依赖注入原理的更多相关文章

  1. 30行代码让你理解angular依赖注入:angular 依赖注入原理

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

  2. [译] 关于 Angular 依赖注入你需要知道的

    如果你之前没有深入了解 Angular 依赖注入系统,那你现在可能认为 Angular 程序内的根注入器包含所有合并的服务提供商,每一个组件都有它自己的注入器,延迟加载模块有它自己的注入器. 但是,仅 ...

  3. Angular依赖注入详解

    Angular算是将后端开发工程化引入前端的先驱之一,而Dependency injection依赖注入(后面简称为DI)又是Angular内部运作的核心功能,所以要深入理解Angular有必要先理解 ...

  4. PHP依赖注入原理与用法分析

    https://www.jb51.net/article/146025.htm 本文实例讲述了PHP依赖注入原理与用法.分享给大家供大家参考,具体如下: 引言 依然是来自到喜啦的一道面试题,你知道什么 ...

  5. Spring依赖注入原理分析

    在分析原理之前我们先回顾下依赖注入的概念: 我们常提起的依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念.具体含义是:当某个角色( ...

  6. 【串线篇】spring泛型依赖注入原理

    spring泛型依赖注入原理 不管三七二十一 servlet :加注解@servlet service:加注解@service dao:加注解@Repository 这相当于在容器中注册这些个类

  7. Angular依赖注入:全面讲解(翻译中)

    在实际使用Angular依赖注入系统时,你需要知道的一切都在本文中.我们将以实用易懂并附带示例的形式解释它的所有高级概念. Angular最强大.最独特的功能之一就是它内置的依赖注入系统. 大多数时候 ...

  8. 理论+案例,带你掌握Angular依赖注入模式的应用

    摘要:介绍了Angular中依赖注入是如何查找依赖,如何配置提供商,如何用限定和过滤作用的装饰器拿到想要的实例,进一步通过N个案例分析如何结合依赖注入的知识点来解决开发编程中会遇到的问题. 本文分享自 ...

  9. angularjs 依赖注入原理与实现

    在用angular依赖注入时,感觉很好用,他的出现是 为了“削减计算机程序的耦合问题” ,我怀着敬畏与好奇的心情,轻轻的走进了angular源码,看看他到底是怎么实现的,我也想写个这么牛逼的功能.于是 ...

随机推荐

  1. 在Vi里面实现字符串的批量替换

    在Vi里面实现字符串的批量替换. a. 文件内全部替换: %s#abc#def#g(用def替换文件中所有的abc) 例如把一个文本文件里面的"linuxidc.com"全部替换成 ...

  2. jQuery 选择同时包含两个class的元素的实现方法

    Jquery选择器 多个 class属性参照以下案例 <element class="a b good list card"> 1. 交集选择: $(".a. ...

  3. chkdsk磁盘修复命令工具怎么用,怎样运行chkdsk工具修复?

    Chkdsk是系统检查磁盘当前状态的一个命令,启动它可以显示磁盘状态.内存状态和指定路径下指定文件的不连续数目.选择“开始→运行”输入“Chkdsk”回车,即可启动Chkdsk,它会自动校验文件并将丢 ...

  4. Mvc视图引擎、寻址规则

    目前MVC中用的较多的视图引擎应该是WebFormViewEngine和RazorViewEngine了. 一个Request请求首先会进入Routing进行判断,对于错误的url是不能被路由匹配到的 ...

  5. cocos2d-x打飞机实例总结

    写了一个cocos2d-x的打飞机游戏,为了深入了解,准备进入引擎内部,深入分析一下打飞机,顺便梳理一下相关的知识 打算分为几个部分: 1.程序入口和场景切换模块分析:简单了解HelloWorld怎样 ...

  6. 平衡二叉查找树(AVL)的理解与实现

    AVL树的介绍 平衡二叉树,又称AVL(Adelson-Velskii和Landis)树,是带有平衡条件的二叉查找树.这个平衡条件必须要容易保持,而且它必须保证树的深度是 O(log N).一棵AVL ...

  7. SDL 1.2.15 issue

    SDL 1.2.15中,对于X11的函数,默认采用动态加载的方式 但相应的X11函数名在SDL中并没有重新命名(SDL2中都添加了前缀X11_) 这样在SDL与其他库混合静态编译链接时,X11的函数就 ...

  8. 什么是pe系统

    Winpe全称 Windows Preinstall Environment,即“Windows 预安装环境”.是一个用于Windows 安装准备的最小操作系统. 基于保护模式下运行Windows X ...

  9. 《JavaScript DOM编程艺术(第二版)》读书总结

    这本书是一本很基础的书,但对于刚入前端不久的我来说是一本不错的书,收获还是很大的,对一些基础的东西理解得更加透彻了. 1.DOM即document object model的缩写,文档对象模型,Jav ...

  10. Java排序算法——桶排序

    文字部分为转载:http://hxraid.iteye.com/blog/647759 对N个关键字进行桶排序的时间复杂度分为两个部分: (1) 循环计算每个关键字的桶映射函数,这个时间复杂度是O(N ...