依赖注入(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. ubuntu下配置vpn

    Ubuntu系统下搭建VPN环境 以下是基于Amazon EC2/Ubuntu搭建PPTPD服务提供VPN连接的过程记录.至于为什么要搞VPN,大家都懂的...而我主要是要访问Python的一些网站以 ...

  2. 设置 github 帐号user.name和邮箱user.email

    git config --global user.name username git config --global user.email username@email.com

  3. css 拾遗

    1, 实现尖角 <style> .up{ border-top: 30px solid red; border-right:30px solid gold; border-bottom:3 ...

  4. java制作验证码

    建立一个web工程

  5. log4j配置文件加载

    log4j的jar包内部包含preference默认配置,使用者可以通过log4j.xml或log4j.properties来指定自己的配置.xml比properties优先.另外注意java读取pr ...

  6. git 代码更新

    第一:先说首次使用 意思就是这个文件夹中的代码你还没有向GITHUB提交过代码 cd /home/test(假如 test就是你的用户名)/githubtest(这是个文件夹,你可以提前先建立好,这个 ...

  7. 牛顿法|阻尼牛顿法|拟牛顿法|DFP算法|BFGS算法|L-BFGS算法

    一直记不住这些算法的推导,所以打算详细点写到博客中以后不记得就翻阅自己的笔记. 泰勒展开式 最初的泰勒展开式,若  在包含  的某开区间(a,b)内具有直到n+1阶的导数,则当x∈(a,b)时,有: ...

  8. web前端基础知识-(一)html基本操作

    1. HTML概述 HTML是英文Hyper Text Mark-up Language(超文本标记语言)的缩写,他是一种制作万维网页面标准语言(标记).相当于定义统一的一套规则,大家都来遵守他,这样 ...

  9. PHP5.6启动失败

    PHP编译安装完毕,启动失败,提示 1 [23-Jun-2014 12:27:02] ERROR: failed to open configuration file '/usr/local/php/ ...

  10. 文件夹锁定(Source)

    文件夹锁定(Source)private void Lock(string folderPath){    try    {        string adminUserName = Environ ...