使用 TypeScript 装饰器装饰你的代码

装饰器让程序员可以编写元信息以内省代码。装饰器的最佳使用场景是横切关注点——面向切面编程。

面向切面编程(AOP)是一种编程范式,它允许我们分离横切关注点,藉此达到增加模块化程度的目标。它可以在不修改代码自身的前提下,给已有代码增加额外的行为(通知)。

上面的代码展示了装饰器多么具有声明性。下面我们将介绍装饰器的细节:

什么是装饰器?它的目的和类型

装饰器的签名

方法装饰器

属性装饰器

参数装饰器

访问器装饰器

类装饰器

装饰器工厂

元信息反射 API

结语

什么是装饰器?它的目的和类型

装饰器是一种特殊的声明,可附加在类、方法、访问器、属性、参数声明上。

装饰器使用 的形式,其中 必须能够演算为在运行时调用的函数,其中包括装饰声明信息。

它起到了以声明式方法将元信息添加至已有代码的作用。

装饰器类型及其执行优先级为

类装饰器——优先级 4 (对象实例化,静态)

方法装饰器——优先级 2 (对象实例化,静态)

访问器或属性装饰器——优先级 3 (对象实例化,静态)

参数装饰器——优先级 1 (对象实例化,静态)

注意,如果装饰器应用于类构造函数的参数,那么不同装饰器的优先级为:1. 参数装饰器,2. 方法装饰器,3. 访问器或参数装饰器,4. 构造器参数装饰器,5. 类装饰器。

我们看到,上面的代码中, 和 返回了另一个函数(装饰器函数)。 和 称为装饰器工厂。

装饰器工厂帮助用户传递可供装饰器利用的参数。

我们还可以看到,演算顺序由顶向下执行顺序由底向上

装饰器的签名

方法装饰器

从上面的签名中,我们可以看到方法装饰器函数有三个参数:

target—— 当前对象的原型,也就是说,假设 Employee 是对象,那么 target 就是

propertyKey—— 方法的名称

descriptor—— 方法的属性描述符,即

上面的代码应该算是自解释的——让我们看看编译后的 JavaScript 是什么样的。

让我们开始分析 Employee 函数——构造器初始化 参数和 方法,将其加入原型。

这是 TypeScript 自动生成的通用方法,它根据装饰器类型和相应参数处理装饰器函数调用。

该函数有助于内省方法调用,并为开发者铺平了处理类似日志记忆化应用配置等横切关注点的道路。

在这个例子中,我们仅仅打印了函数调用及其参数、响应。

注意,阅读 方法中的详细注释可以理解其内部机制。

属性装饰器

属性装饰器函数有两个参数:

target—— 当前对象的原型,也就是说,假设 Employee 是对象,那么 target 就是

propertyKey—— 属性的名称

上面的代码中,我们在装饰器中内省属性的可访问性。下面是编译后的代码。

参数装饰器

参数装饰器函数有三个参数:

target—— 当前对象的原型,也就是说,假设 Employee 是对象,那么 target 就是

propertyKey—— 参数的名称

index—— 参数数组中的位置

在上面的代码中,我们收集了所有被装饰的方法参数的索引或位置,作为元数据加入对象的原型。下面是编译后的代码。

类似之前见过的 函数, 函数返回一个封装参数装饰器的装饰器。

如我们所见,调用参数装饰器时,会忽略其返回值。这意味着,调用 函数时,其返回值不会用来覆盖参数值。

这就是参数装饰器不返回的原因所在。

访问器装饰器

访问器不过是类声明中属性的读取访问器和写入访问器。

访问器装饰器应用于访问器的属性描述符,可用于观测、修改、替换访问器的定义。

上面的例子中,我们定义了两个访问器 和 ,并通过装饰器设置是否将其列入清单,据此决定对象的行为。 将列入清单,而 不会。

注意:TypeScript 不允许同时装饰单一成员的 和 访问器。相反,所有成员的装饰器都必须应用于首个指定的访问器(根据文档顺序)。这是因为装饰器应用于属性描述符,属性描述符结合了 和 访问器,而不是分别应用于每项声明。

下面是编译的代码。

类装饰器

类装饰器应用于类的构造器,可用于观测、修改、替换类定义。

上面的装饰器声明了一个名为 的变量,将其值设为被装饰的类构造器。

接着声明了名为 的辅助函数。该函数用于创建类的实例。

我们接下来创建了一个名为 的变量,该变量将用作新构造器。该函数调用原构造器,同时在控制台打印实例化的类名。这正是我们给原构造器加入额外行为的地方。

原构造器的原型复制到 ,以确保创建一个 Employee 新实例的时候, 操作符的效果符合预期。

新构造器一旦就绪,我们便返回它,以完成类构造器的实现。

新构造器就绪之后,每次创建实例时会在控制台打印类名。

编译后的代码如下。

在编译后的代码中,我们注意到两处不同:

如你所见,传给 的参数有两个,装饰器数组和构造器函数。

TypeScript 编译器使用 的返回值以覆盖原构造器。

这正是类装饰器必须返回一个构造函数的原因所在。

装饰器工厂

由于每种装饰器都有它自身的调用签名,我们可以使用装饰器工厂来泛化装饰器调用。

元信息反射 API

元信息反射 API (例如 )能够用来以标准方式组织元信息。

「反射」的意思是代码可以侦测同一系统中的其他代码(或其自身)。

反射在组合/依赖注入、运行时类型断言、测试等使用场景下很有用。

上面的代码用到了reflect-metadata这个库。其中,我们使用了反射元信息的设计键(例如:)。目前只有三个:

类型元信息用了元信息键 。

参数类型元信息用了元信息键 。

返回类型元信息用了元信息键 。

有了反射,我们就能够在运行时得到以下信息:

实体

实体类型

实体实现的接口

实体构造器参数的名称和类型。

结语

装饰器不过是在设计时(design time)帮助内省代码,注解及修改类和属性的函数。

Yehuda Katz 提议在 ECMAScript 2016 标准中加入装饰器特性:tc39/proposal-decorators

我们可以通过装饰器工厂将用户提供的参数传给装饰器。

有 4 种装饰器:装饰器、方法装饰器、属性/访问器装饰器、参数装饰器。

元信息反射 API有助于以标准方式在对象中加入元信息,以及在运行时获取设计类型信息

我把文中所有代码示例都放到了mohanramphp/typescript-decorators这个 Git 仓库中。谢谢阅读!

题图:Alex Loup

其他内容推荐

6 款热门 macOS 开源应用

22 个 iOS 开发热门开源项目

你可能不知道的 npm 实用技巧

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20191119A07TDU00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码关注腾讯云开发者

领取腾讯云代金券