这一章是真的坑,感觉单独看教程很无聊,不好理解,感觉有些没什么用处,可能好多都没有实际用途。个人建议最好还是从实际用途出发,来学习装饰器

JS里的装饰器(Decorator)目前处于意见征集第二阶段(stage 2),属于试验性特性,未来的版本中可能发生改变。

Decorator 是一种不错的AOP(面向切面编程)方案,AOP是一种编程思想核心是: 非侵入式增强,可以在运行时动态的将代码切入到类的指定方法、指定位置上。

Decorator本质上就是函数的语法糖,设计模式中有: 装饰者模式,关于JS装饰者模式与AOP,可以参考JS设计模式与开发实践第15章装饰者模式

JS是一门弱语言类型,与强语言类型相比,最大的编程陋习就是对类型思维的缺失。学习ts,最重要的就是类型思维的重塑。

基本配置

Decorator在TS中还是一项实验性功能,所以在TS的配置文件中需要加一个参数

  1. 命令行
  1. tsc --target ES5 --experimentalDecorators ts文件
  1. tsconfig.json
  1. {
  2. "compilerOptions": {
  3. "target": "ES5",
  4. "experimentalDecorators": true
  5. }
  6. }

在TS中,Decorator可以修饰5种语句:类、属性、方法、访问器、方法参数。

用装饰器装饰类

  • 类装饰器不能在声明文件(.d.ts)中
  • 类装饰器会在运行时当作函数被调用,类的构造函数作为其唯一的参数

用装饰器装饰类可以做什么

类装饰器可以获取到类的constructor, 那我们怎么可以在无侵入的情况,对类进行增强呢?需要思考拿到constructor可以做什么?

  1. // 理论上,每次new新的对象,都会调用该类的constructor函数,改变constructor函数,那可能会对每次new对象都会产生影响
  2. // 我写了个js的demo来修改constructor来看效果,结果发现constructor是不可修改的(修改不会生效)
  3. // 所以我们首先要转变思想,装饰器可以改变constructor的内容,让我们做一些运行时非侵入增强
  4. class A {
  5. constructor() {
  6. this.a = 1
  7. }
  8. }
  9. // 尝试改变A的constructor
  10. console.log(A.constructor)
  11. A.constructor = function() {
  12. console.log('constructor 改变')
  13. this.a = 2
  14. }
  15. let a = new A()
  16. console.log(a) // A { a: 1 }

没有返回值的类装饰器

密封构造函数(constructor)及其原型,或者在constructor的原型上新增内容

  1. // 原例子:当@sealed被执行时,他将密封类的构造函数和原型
  2. // js中constructor本来就是密封的,无法修改,这里尝试再constructor.prototype上增加变量和函数
  3. // 这些新增的功能,在new的对象上都有生效
  4. @sealed
  5. class Gretter {
  6. greeting: string;
  7. constructor(message: string) {
  8. this.greeting = message;
  9. }
  10. greet() {
  11. return `hello, ${this.greeting}`
  12. }
  13. }
  14. // @sealed装饰器定义
  15. function sealed(constructor: Function) {
  16. console.log('开始了装饰器增强')
  17. constructor.prototype.a = 1
  18. constructor.prototype.sayNo = function() {
  19. return 'No'
  20. }
  21. // Object.seal(constructor)
  22. // Object.seal(constructor.prototype)
  23. }
  24. let gretter = new Gretter('tom')
  25. console.log(gretter) // Gretter { greeting: 'tom' }
  26. console.log(gretter.greet()) // hello, tom
  27. console.log(gretter.a) // 1
  28. console.log(gretter.sayNo()) // No
  29. // 这里为了看看 console.log('开始了装饰器增强'),是否每次在new时都会执行
  30. // 事实是就算不调用new,console.log('开始了装饰器增强') 也只会执行一次,每次new并不会调用装饰器方法
  31. // 而是在程序运行时,装饰器函数只执行一次,动态的对类进行增强性修改
  32. let gretter2 = new Gretter('jack')
  33. console.log(gretter2.sayNo()) // No

有返回值的类装饰器

如果类装饰器返回一个值,需要返回一个新的构造函数,必须处理好原来的原型链,原来的构造函数会被替换,这样我们可以在每次new时加一个log什么的,下面来看例子

  1. @classDecorator
  2. class Greeter {
  3. property = 'property';
  4. hello: string;
  5. constructor(m: string) {
  6. this.hello = m
  7. }
  8. }
  9. console.log(new Greeter('world')) // 未加装饰器之前:Greeter { property: 'property', hello: 'world' }
  10. console.log(new Greeter('world2'))
  11. // { new (...args: any[]): {} } 表示一个构造函数 类型
  12. function classDecorator<T extends {new(...args:any[]):{}}>(constructor: T) {
  13. // 每次执行都会执行下面函数体的内容
  14. return class extends constructor {
  15. constructor(...args: any[]) {
  16. super(...args)
  17. console.log('new 发生了')
  18. }
  19. newProperty = 'new Property';
  20. // hello = 'override'; // 如果打开,所有的hello都将是override
  21. }
  22. }
  23. // new 发生了
  24. // class_1 {
  25. // property: 'property',
  26. // hello: 'world',
  27. // newProperty: 'new Property' }
  28. // new 发生了
  29. // class_1 {
  30. // property: 'property',
  31. // hello: 'world2',
  32. // newProperty: 'new Property' }

用装饰器装饰属性

装饰属性,基本是一次性操作,运行到装饰器当做函数运行后,基本只执行了一次,函数会接收两个参数

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  2. 成员的key
  1. function setDefaultValue(target: Object, propertyName: string) {
  2. // 对于不同属性,target为类的原型对象
  3. console.log(target, propertyName) // Person {} 'name'
  4. target[propertyName] = "Tom";
  5. }
  6. function setDefaultValueStatic(target: Object, propertyName: string) {
  7. // 对于静态属性,target为类的构造函数
  8. console.log(target, propertyName) // { [Function: Person] displayName: 'PersonClass' } 'displayName'
  9. target[propertyName] = "Static Tom";
  10. }
  11. class Person {
  12. @setDefaultValue
  13. name: string;
  14. @setDefaultValueStatic
  15. static displayName = 'PersonClass'
  16. }
  17. console.log(new Person()) // Person {}
  18. console.log(new Person().name); // Tom
  19. console.log(Person.displayName); // Static Tom

用装饰器装饰方法

注意方法(method)与函数(function)的区别,方法一般指对象的方法,一般有公共方法、私有方法等; 函数一般是独立的函数

用装饰器装饰函数,可以想象,就是再返回一个新的函数,如果不返回新的函数,你只能对装饰器自己传入的参数进行处理,没什么用,所以装饰器装饰函数一般都是需要return一个新的函数,且这个函数会接收三个参数:

  1. 类的构造函数(对于静态函数来说) 或者 类的原型对象(对于普通实例函数来说)
  2. 成员(函数)的名称
  3. 成员(函数变量)的属性描述符 (如果代码输出目标版本小于ES5,属性描述符会是undefined)
  1. class Greeter {
  2. greeting: string;
  3. constructor(message: string) {
  4. this.greeting = message
  5. }
  6. @enumerable(false)
  7. greet() {
  8. return `Hello, ${this.greeting}`
  9. }
  10. }
  11. function enumerable(value: boolean) {
  12. // console.log(value)
  13. return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  14. console.log('返回的新方法')
  15. console.log(target, propertyKey, descriptor)
  16. // Greeter { greet: [Function] }
  17. // 'greet'
  18. // { value: [Function],
  19. // writable: true,
  20. // enumerable: true,
  21. // configurable: true }
  22. // descriptor.enumerable = value; 原demo比较鸡肋,这个貌似都是不可枚举的
  23. // 用Refect.ownKeys() 才能遍历出 返回自身的所有属性、函数,不管是否可枚举,包括symbol
  24. // 关于for...in,Object.keys()遍历的区别,参见原来的笔记: https://www.yuque.com/guoqzuo/js_es6/rxu7ms
  25. // 那我们还是改 descriptor 的value吧
  26. const method = descriptor.value // 将函数的值 () => { return `Hello, ${this.greeting}`} 保存到变量里
  27. descriptor.value = function(...args: any[]) {
  28. // 先执行原来的方法
  29. let result = method.apply(this, args)
  30. // 增强部分
  31. // 这里可以执行其他的一些额外操作
  32. console.log('原计算结果为: ', result)
  33. let newRes = 'No, ' + this.greeting
  34. console.log('但我在这里把结果改为了', newRes)
  35. return newRes
  36. }
  37. };
  38. }
  39. // 这里会打印: 返回的新方法
  40. let a = new Greeter('hello')
  41. console.log(a)
  42. console.log(Object.keys(a))
  43. console.log(a.greet())
  44. let a2 = new Greeter('hello2')
  45. console.log(a2)
  46. console.log(Object.keys(a2))
  47. console.log(a2.greet())
  48. // Greeter { greeting: 'hello' }
  49. // [ 'greeting' ]
  50. // 原计算结果为: Hello, hello
  51. // 但我在这里把结果改为了 No, hello
  52. // No, hello
  53. // Greeter { greeting: 'hello2' }
  54. // [ 'greeting' ]
  55. // 原计算结果为: Hello, hello2
  56. // 但我在这里把结果改为了 No, hello2
  57. // No, hello2

用装饰器装饰访问器(getter或setter)

  1. function Enumerable( target: any, propertyKey: string, descriptor: PropertyDescriptor ) {
  2. //make the method enumerable
  3. descriptor.enumerable = false; // 本来就是可迭代的,我们设置为不可迭代
  4. // 无法修改value
  5. // TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
  6. // descriptor.value = function(...args: any[]) {
  7. // // const method = descriptor.value
  8. // // let result = method.apply(this, args)
  9. // // return this._name + '无脑赋值'
  10. // }
  11. }
  12. class Person {
  13. _name: string;
  14. constructor(name: string) {
  15. this._name = name;
  16. }
  17. // @Enumerable
  18. get name() {
  19. return this._name;
  20. }
  21. }
  22. console.log("-- creating instance --");
  23. let person = new Person("tom");
  24. console.log("-- looping --");
  25. for (let key in person) {
  26. console.log(key + " = " + person[key]);
  27. }
  28. // 不加装饰器的情况
  29. // -- creating instance --
  30. // -- looping --
  31. // _name = tom
  32. // name = tom
  33. // 加了装饰器的情况
  34. // -- creating instance --
  35. // -- looping --
  36. // _name = tom

用装饰器装饰函数参数

看官方示例,函数参数装饰器结合函数装饰器,进行校验。待后续研究
有三个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字
  • 参数在函数参数列表中的索引
class Greeter {
  greeting: string;

  constructor(message: string) {
      this.greeting = message;
  }

  // @validate
  greet(@required name: string) {
      return "Hello " + name + ", " + this.greeting;
  }
}

function required(...args) {
  console.log(args) // [ Greeter { greet: [Function] }, 'greet', 0 ]

}

装饰器执行顺序

一个类中,不同位置声明的装饰器,按照以下规定的顺序应用:

  • 有多个参数装饰器(parameterDecorator)时,从最后一个参数依次向前执行
  • 方法(methodDecorator)和方法参数装饰器(parameterDecorator)中,参数装饰器先执行
  • 类装饰器(classDecorator)总是最后执行。
  • 方法(methodDecorator)和属性装饰器(propertyDecorator),谁在前面谁先执行。

参考