Angular DI 是怎么工作的?

栏目: JavaScript · 发布时间: 5年前

内容简介:这又是一篇短命的(short-lived)描述当下技术框架的博客文章。 DI(依赖注入、Dependency injection)是一种设计模式,常用来提升代码的可复用性、健壮性和可测试性。 DI 在前端的流行很大程度上归功于它在 Angular1.x 中的应用。 Angular 2 以后借助 TypeScript 的类型分析,可以干脆省去了 Angular1.x 中冗余的依赖声明。 本文用来解释这个魔法是怎么工作的,以及相关的标准化 Proposal 和实现 Trick。对于不熟悉依赖注入的同学可以参考我

这又是一篇短命的(short-lived)描述当下技术框架的博客文章。 DI(依赖注入、Dependency injection)是一种设计模式,常用来提升代码的可复用性、健壮性和可测试性。 DI 在前端的流行很大程度上归功于它在 Angular1.x 中的应用。 Angular 2 以后借助 TypeScript 的类型分析,可以干脆省去了 Angular1.x 中冗余的依赖声明。 本文用来解释这个魔法是怎么工作的,以及相关的标准化 Proposal 和实现 Trick。

对于不熟悉依赖注入的同学可以参考我的另外两篇文章 什么时候应该使用依赖注入JavaScript 依赖注入实现 , 或者 Wikipedia: DI 。这里还有一篇 Angular 的教程: https://angular.io/guide/dependency-injection

一个例子

Angular1.x

为了方便说明,我们先给一个 Angular1 Dependency Injection 的例子:

angular.module('bar').controller('Bar', function (Foo) {})

在使用上述组件(Bar)时,Angular DI 会创建一个 Foo 并用来初始化 Bar 的实例。 Angular 使用 Function.prototype.toString 来获取控制器 Bar 的参数需要哪些类型。 因此如果上述代码经过 uglify 之后依赖注入会不起作用。因此有另一种写法:

// 因为字符串字面量不会被 uglify,相当于通过字符串字面量来声明依赖
angular.module('bar').controller('Bar', ['Foo', function (Foo) {}])

Angular with TypeScript

再看 TypeScript 下的 Angular2 代码,省去了这个字面量的声明:

import { Foo } from './foo'

@Component({...})
class Bar {
  constructor(private foo: Foo) {}
}

上述 TypeScript 代码中 Bar 只依赖 Foo 的类型, 这意味着 TypeScript 编译上述代码得到的文件中没有对 Foo 的引用。 这里的魔法在于:Angular DI 是怎么在运行时知道 Bar 的第一个参数类型是 Foo?

装饰器

装饰器是一种用来提供面向切面编程功能的一种语法构造。 目前是一个 Stage2 Proposal ,还没有进入 ECMA 标准。

在 TypeScript 可以打开 experimentalDecorators 选项来使用。 它的原理就是调用自定义的一个高阶函数,来操作被装饰的函数。 可以参考 TypeScript 教程: https://www.typescriptlang.org/docs/handbook/decorators.html 。 比如上面的 Component 装饰器,它会在运行时对 Bar 做一个转换。比如这样:

var __decorator = function() {/*...*/}
var Bar = function (foo) {
    this.foo = foo
}
Bar = __decorate([Component], Bar)

function Component(Bar) {
    // 对 Bar 进行一些操作,甚至返回一个新的类替代原来的 Bar
}

借助于装饰器的机制,我们就有机会在 Component 函数中标记 Bar 的依赖信息。 如果你是分模块编译的,可能需要避免重复输出 __decorator 函数的定义。 设置 importHelpers 编译器参数可以禁止它输出, 然后在运行时统一引入 tslib 。 当然如果你在使用 Angular,则应该引入 Angular Polyfill。

获取依赖信息

恰好 TypeScript 提供来一个 emitDecoratorMetadata 开关来产出编译时的类型信息给装饰器。 目前只提供三种类型信息:

  • “design:type”
  • “design:paramtypes”
  • “design:returntype”。

参考这个 Issue https://github.com/Microsoft/TypeScript/issues/2577 。 打开 emitDecoratorMetadata 开关后编译后代码会多这样一部分内容:

var Bar = __decorate([
    __metadata('design:paramtypes', [foo_1.Foo])
], Bar);

可以看到它把 Bar 的参数类型 Foo 传给来 __metadata 这个 Helper。 这里和 Angular1.x 的不同还在于这里存的类型就是对函数 Foo 的引用,不是某种序列化后的字符串。 这意味着 Angular DI 不依赖于序列化后的字符串和被注入函数之间的对应关系; 但是也意味着这个类型只能是 class、string、object 这样的既存在于编译期也存在于运行时的类型。 比如 Interface 和 Type 这样的类型信息就无法记录,参考: https://github.com/microsoft/TypeScript/issues/3015#issuecomment-98852421

接下来的问题就是, __metadata 怎么存储这个类型信息 Foo ,最终怎么让 Angular DI 读取到。 由于 Angular DI 容器需要在运行时使用这个信息,这就需要 JavaScript 的反射机制。

Metadata Reflection API

反射 用来在运行时检查和操作对象,是 ES6 标准的一部分。 而 Reflect Metadata 提案 则是让 Reflect API 可以提供对类型的元数据进行操作的方法, 需要引入 Polyfill: reflect-metadata

元数据反射机制的灵感来源是 C#、 Java 之类的语言。 它们可以在源代码中通过一些标注来给类型设置元数据,并用反射 API 在运行时读取。 总之 Reflect 就是用来存这些元数据(类型信息)的地方。 下面看一下 __metadata 是怎样使用 Metadata Reflection API 的:

var __metadata = this && this.__metadata || function (k, v) {
    if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function')
        return Reflect.metadata(k, v);
};

可以看到 __metadata Helper 把类型信息存到来 Reflect.metadata API 中。

Angular DI 中获取类型信息

既然类型信息已经存入 Reflect API,Angular DI 就可以在创建 Bar 的时候拿到它的参数类型信息了。比如这样:

function create(Bar) {
    const types = Reflect.getMetadata('design:paramtypes', Bar)
    const args = types.map(Type => new Type())
    return new Bar(...args)
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法

数据结构与算法

Michael McMillan / 吕秀峰、崔睿 / 人民邮电出版社 / 2009-5 / 49.00元

《数据结构与算法C#语言描述》是在.NET框架下用C#语言实现数据结构和算法的第一本全面的参考书。《数据结构与算法C#语言描述》介绍的方法非常实用,采用了时间测试而非大O表示法来分析算法性能。内容涵盖了数据结构和算法的基本原理,涉及数组、广义表、链表、散列表、树、图、排序搜索算法以及更多概率算法和动态规则等高级算法。此外,书中还提供了.NET框架类库中的C#语言实现的数据结构和算法。 《数据......一起来看看 《数据结构与算法》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具