TypeScript教程(六)– 装饰器

​装饰器允许我们修改或增强我们的类,在本文中,我们将介绍什么是装饰器,类装饰器,方法装饰器,属性装饰器,访问器装饰器,参数装饰器等。


什么是装饰器

装饰器应用于类及其成员,并可以改变它们的行为,经常应用于Angular或Vue等前端框架。如下:

@Component

class ProfileComponent{

}

我们用@Component装饰器来装饰ProfileComponent类,将其变成一个组件,目前我们未实现@Component装饰器,当然会有编译错误。

装饰器实际上就是一个被JavaScript引擎调用的函数,当被调用时,它将装饰的类传递给JavaScript引擎,在装饰器的函数里,我们有机会修改类,可以添加属性、添加方法甚至修改已存在的方法。

装饰器是一个实验特性,未来可能会有变化,我们需要在tsconfig.json中开启选项。

“experimentalDecorators”: true,


类装饰器

前面说过,装饰器是一个函数,它采用Pascal命名法,即每个单词首字母大写。

装饰器参数的个数和类型根据装饰的对象而不同,装饰一个类class只需提供一个参数,代表构造函数,可以任意命名,但建议还是按约定命名为constructor。命名不重要,重要的是类型,类型选择Function。

JavaScript引擎看到装饰器参数的类型为Function,就认为要应用到一个类class上,该Function类型的参数就代表构造函数。

在这个Function里,我们可以随意修改和增强我们的类,没有任何限制。

我们知道,每个JavaScript对象都一个原型prototype,它从原型那里继承各种属性和方法。上图中,我们使用prototype添加了新的属性uniqueId和新方法insertInDom,所有应用该装饰器的类都会增加uniqueId和insertInDom,当然我们的ProfileComponent类的实例也会增加这两个新成员。

其实我们完全可以通过继承来达到同样效果。如果使用继承,可以创建一个基类Component, 再在其中定义insetInDom等,然后ProfileCompoent继承于Componet。

编译和运行。

tsc && node dist/index.js

我们看到,即便我们还未生成ProfileComponent的实例,也输出了

‘Component decorator called’

不管我们创建0个或10个实例,装饰器函数都只执行一次。

另外,我们也可以看一下生成的JavaScript代码,看看装饰器都有什么魔法。


参数化装饰器

有时候,我们希望传递参数给装饰器,如@Component(1)。

可以创建一个函数,接受参数,并返回一个装饰器。

返回的装饰器,我们用箭头函数来简化代码。由于返回装饰器,我们称这样的函数为装饰器工厂。

我们再提一个水平,Component接收一个对象,这种用法在一些前端框架比如Angular里可能经常会看到。

如果您理解不了这种花哨的用法也没关系,只需要知道装饰器可以接受对象作为参数。


装饰器组合

我们可以将多个装饰器应用到一个类或它的成员上。

如上图,新增一个装饰器Pipe并应用到ProfileComponent上。

运行,发现两个装饰器都生效了。

不过运行的顺序跟放置的顺序是反的,这其实很好理解,因为装饰器实际是函数,多个装饰器相当于一个函数的返回结果当作另一个装饰器函数的参数。

类似 f(g(x))

x是Pipe函数,g是Component函数,x先执行。


方法装饰器

方法装饰器应用在类的方法上,需要3个参数,谷歌搜索”typescript decorators”,查看网页了解用法。

https://www.typescriptlang.org/docs/handbook/decorators.html

第1个参数是指拥有该方法的对象,使用了any类型,之前我们说过尽量避免使用any,但这里可以使用。

第2个参数是指方法的名称。

第3个参数是方法的描述对象,在JavaScript中,对象里的每个属性都有一个描述器用于描述该属性。

参数的命名不重要,重要的是参数数量和类型。

我们完全可以替换掉原有方法而使用一个全新的实现,但这是核按钮,不能随意这么做,而是应该在原有方法的基础上进行增强。我们先保存原有的方法,为使编辑器有智能感知,用as 指定类型为Function。

const original = descriptor.value as Function;

然后对该方法进行扩展,再用call方法调用回原来的方法。

original.call(this, ‘Blue Sky’)

call第1个参数固定是 this,第2个参数这里我们用了硬编码”Blue Sky”,所以生成Person的实例person并调用 say(‘Hello’)时,输出的”Hello”被忽略了而是显示“Blue Sky”。

如果需要使用外部传过来的参数如”Hello”,就在function里添加参数。

… = function (message: string){…}

但这种实现还不够灵活,我们需要更通用些。

使用剩余操作符 … 实现。

装饰符 Log的实现,使用了function,有人可能想改成箭头函数,但这里是不行的,因为original的call的方法里,第1个参数是this,箭头函数没有自己的this关键字,所以就要用常规function的方式。


访问器装饰器

访问器装饰器是指应用在get上的装饰器。

访问器实际上也是方法,所以访问器装饰器的参数与方法装饰器是一样的,在获取原来方法时,不使用方法装饰器PropertyDescriptor的value,而是用get ,这点是有区别的。

上图我们使用Capitalize装饰器将fullName变成大写。在代码实现中,我们检测原get获取的结果,如果类型是string,就变成大写,其它类型的话,不作更改,增加代码的健壮性。测试get fullName返回 0 和 null时,程序也能很好运行不崩溃。


属性装饰器

如下图,我们给User类的password属性添加装饰器要求长度不能小于4个。

因为带参数,创建一个装饰器工厂MinLength,接收length参数,然后返回一个属性装饰器。属性装饰器与方法装饰器类似,但只有2个参数,没有PropertyDescriptor参数。

在装饰器函数里,我们创建一个PropertyDescriptor对象,该对象有get, set 函数。set函数里,我们判断属性长度,如果小于4将抛出异常,将正常的值保存在变量value里。get函数获取value值。

调用Object的defineProperty方法,用创建的PropertyDescriptor代替原有属性。

我们可以测试下,如果构造函数提供的password长度小于4,或者已生成的实例设置password的长度小于4,都将抛出异常,达到了我们的预期。


参数装饰器

坦率地说,参数装饰器是不常用的,除非你要设计一个框架供别人使用。

上图中,我们给Vehicle类的move方法的参数speed添加装饰器@Watch。参数装饰器有3个参数,前2个我们都见过了,第3个参数parameterIndex也已自我解释了,表示第几个参数。

在参数装饰器中,确定没什么工作可做,一般就是记录一些元数据,这里我们就记录参数所在的方法名是什么,以及是第几个参数。


小结

本文我们介绍了如何使用装饰器修改或增强我们的类,内容涉及了类装饰器,方法装饰器,属性装饰器,访问器装饰器,参数装饰器等等。

下一篇介绍TypeScript模块相关知识。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注