ES Decorators简介

501 查看

我跟你说,我最讨厌“简介”这种文章了,要不是语文是体育老师教的,早就换标题了!

Decorators是ECMAScript现在处于Stage 1的一个提案。当然ECMAScript会有很多新的特性,特地介绍这一个是因为它能够在实际的编程中提供很大的帮助,甚至于改变不少功能的设计。

先说说怎么回事

如果光从概念上来介绍的话,官方是这么说的:

Decorators make it possible to annotate and modify classes and properties at design time.

我翻译一下:

装饰器让你可以在设计时对类和类的属性进行注解和修改。

什么鬼,说人话!

所以我们还是用一段代码来看一下好了:

别去试上面的代码,瞎写的,估计跑不起来就是了。这个代码的作用其实看函数的命名就能明白,我们要给Foo#getFooById方法加一个缓存,缓存使用第一个参数作为对应的键。

可以看出来,上面代码的重点在于:

  1. 有一个memoize函数。
  2. 在类的某个方法上加了@memoize;这样一个标记。

而这个@memoize就是所谓的Decorator,我称之为装饰器。一个装饰器有以下特点:

  1. 首先它是一个函数。
  2. 这个函数会接收3个参数,分别是targetkeydescriptor,具体的作用后面再说。
  3. 它可以修改descriptor做一些额外的逻辑。

看到了基本用法其实并不能说明什么,我们有几个核心的问题有待说明:

有几种装饰器

现阶段官方说有2种装饰器,但从实际使用上来看是有4种,分别是:

  • 放在class上的“类装饰器”。
  • 放在属性上的“属性装饰器”,这需要配合另一个Stage 0的类属性语法提案,或者只能放在对象字面量上了。
  • 放在方法上的“方法装饰器”。
  • 放在gettersetter上的“访问器装饰器”。

其中类装饰器只能放在class上,而另外3种可以同时放在class和属性或者对象字面量的属性上,比如这样也是可以的:

不过注意放在对象字面量时,装饰器后面不能写分号,这是个比较怪异的问题,后面还会说到更怪异的情况,我也在和提案的作者沟通这是为啥。

之所以这么分,是因为不同情况下,装饰器接收的3个参数代表的意义并不相同。

装饰器的3个参数是什么

装饰器接收3个参数,分别是targetkeydescriptor,他们各自分别是什么值,用一段代码就能很容易表达出来:

这是使用babel转换的JavaScript的输出,从这里可以看到:

  1. key很明显就是当前方法名,我们可以推断出来用于属性的时候就是属性名
  2. descriptor显然是一个PropertyDescriptor,就是我们用于defineProperty时的那个东西。
  3. target确实不是那么容易看出来,所以我用了3行代码。首先这是一个对象,然后是一个有constructor属性的对象,最后constructur指向的是Bar这个函数。所以我们也能推测出来这货就是Bar.prototype没跑了。

那如果装饰器放在对象字面量上,而不是类上呢?这边就不再给代码,直接放结论了:

  1. keydescriptor和放在类属性/方法上一样没变,这当然也不应该变。
  2. targetObject对象,相信我你不会想用这个参数的。

当装饰器放在属性、方法、访问器上时,都符合上面的原则,但放在类上的时候,有一些不同:

  1. keydescriptor不会提供,只有target参数。
  2. target会变成Bar这个方法,而不是其prototype

其实对于属性、方法和访问器,真正有用的就是descriptor,其它几个无视问题也不大就是了。而对于类,由于target是唯一能用的,所以会需要它。

对于这一环节,我们需要特别注意一点,由于target是类的prototype,所以往它上面添加属性是,要注意继承时是会被继承下去的,而子类上再加同样属性又会有覆盖甚至对象、数组同引用混在一起的问题。这和我们平时尽量不在prototype上放对象或者数组的思路是一致的,要避免这一问题。

装饰器在什么时候执行

既然装饰器本身是一个函数,那么自然要有函数被执行的时候。

现阶段,装饰器只能放在一个类或者一个对象上,我们可以用代码看一下什么时候执行: