angular依赖注入:angular 依赖注入原理

拼搏现实的明天。 2022-06-06 23:38 375阅读 0赞

依赖注入(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,它会返回函数的源码字符串,这样我们就可以通过正则匹配的方式拿到这个函数的参数列表:

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

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

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

  1. function createInjector(cache) {
  2. this.cache = cache;
  3. }
  4. angular.module = function () {
  5. modules = {};
  6. injector = new createInjector(modules);
  7. return {
  8. injector: injector,
  9. factory: function (name, fn) {
  10. modules[name.trim()] = this.injector.invoke(fn);
  11. return this;
  12. }
  13. }
  14. };

3. 执行时注入

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

  1. createInjector.prototype = {
  2. invoke: function (fn, self) {
  3. argsString = extractArgs(fn);
  4. args = [];
  5. argsString.forEach(function (val) {
  6. args.push(this.cache[val.trim()]);
  7. }, this);
  8. return fn.apply(self, args);
  9. }
  10. };

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

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

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

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

  1. function getService(serviceName, caller) {
  2. if (cache.hasOwnProperty(serviceName)) {
  3. if (cache[serviceName] === INSTANTIATING) {
  4. throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
  5. serviceName + ' <- ' + path.join(' <- '));
  6. }
  7. return cache[serviceName];
  8. } else {
  9. try {
  10. path.unshift(serviceName);
  11. cache[serviceName] = INSTANTIATING;
  12. return cache[serviceName] = factory(serviceName, caller);
  13. } catch (err) {
  14. if (cache[serviceName] === INSTANTIATING) {
  15. delete cache[serviceName];
  16. }
  17. throw err;
  18. } finally {
  19. path.shift();
  20. }
  21. }
  22. }

3. 执行时注入

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

得到参数:

  1. function injectionArgs(fn, locals, serviceName) {
  2. var args = [],
  3. $inject = createInjector.$$annotate(fn, strictDi, serviceName);
  4. for (var i = 0, length = $inject.length; i < length; i++) {
  5. var key = $inject[i];
  6. if (typeof key !== 'string') {
  7. throw $injectorMinErr('itkn',
  8. 'Incorrect injection token! Expected service name as string, got {0}', key);
  9. }
  10. args.push(locals && locals.hasOwnProperty(key) ? locals[key] :
  11. getService(key, serviceName));
  12. }
  13. return args;
  14. }

调用

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

  1. function invoke(fn, self, locals, serviceName) {
  2. if (typeof locals === 'string') {
  3. serviceName = locals;
  4. locals = null;
  5. }
  6. var args = injectionArgs(fn, locals, serviceName);
  7. if (isArray(fn)) {
  8. fn = fn[fn.length - 1];
  9. }
  10. if (!isClass(fn)) {
  11. // http://jsperf.com/angularjs-invoke-apply-vs-switch
  12. // #5388
  13. return fn.apply(self, args);
  14. } else {
  15. args.unshift(null);
  16. return new (Function.prototype.bind.apply(fn, args))();
  17. }
  18. }

angular模块管理,深坑

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

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

  1. 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

发表评论

表情:
评论列表 (有 0 条评论,375人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Angular4--依赖注入

    【背景】 最近在学习Angular,迫不及待想跟学习到的知识跟大家分享。 【内容】 什么是依赖注入? 依赖注入是“控制反转”(将实例化这个类的控制权交给外部,从而实现类

    相关 Angular依赖注入介绍

           依赖注入(DI – Dependency Injection)是一种重要的应用设计模式。Angular里面也有自己的DI框架,在设计应用时经常会用到它,它可以我们