面试官:说说你对 TypeScript 中装饰器的理解?应用场景?

开发 前端
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上,是一种在不改变原类和使用继承的情况下,动态地扩展对象功能。

[[423007]]

一、是什么

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上

是一种在不改变原类和使用继承的情况下,动态地扩展对象功能

同样的,本质也不是什么高大上的结构,就是一个普通的函数,@expression 的形式其实是Object.defineProperty的语法糖

expression求值后必须也是一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入

二、使用方式

由于typescript是一个实验性特性,若要使用,需要在tsconfig.json文件启动,如下:

  1.     "compilerOptions": { 
  2.         "target""ES5"
  3.         "experimentalDecorators"true 
  4.     } 

typescript装饰器的使用和javascript基本一致

类的装饰器可以装饰:

  • 方法/属性
  • 参数
  • 访问器
  • 类装饰

例如声明一个函数 addAge 去给 Class 的属性 age 添加年龄.

  1. function addAge(constructor: Function) { 
  2.   constructor.prototype.age = 18; 
  3.  
  4. @addAge 
  5. class Person{ 
  6.   name: string; 
  7.   age!: number; 
  8.   constructor() { 
  9.     this.name = 'huihui'
  10.   } 
  11.  
  12. let person = new Person(); 
  13.  
  14. console.log(person.age); // 18 

上述代码,实际等同于以下形式:

  1. Person = addAge(function Person() { ... }); 

上述可以看到,当装饰器作为修饰类的时候,会把构造器传递进去。constructor.prototype.age 就是在每一个实例化对象上面添加一个 age 属性

方法/属性装饰

同样,装饰器可以用于修饰类的方法,这时候装饰器函数接收的参数变成了:

  • target:对象的原型
  • propertyKey:方法的名称
  • descriptor:方法的属性描述符

可以看到,这三个属性实际就是Object.defineProperty的三个参数,如果是类的属性,则没有传递第三个参数

如下例子:

  1. // 声明装饰器修饰方法/属性 
  2. function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) { 
  3.   console.log(target); 
  4.   console.log("prop " + propertyKey); 
  5.   console.log("desc " + JSON.stringify(descriptor) + "\n\n"); 
  6.   descriptor.writable = false
  7. }; 
  8.  
  9. function property(target: any, propertyKey: string) { 
  10.   console.log("target", target) 
  11.   console.log("propertyKey", propertyKey) 
  12.  
  13. class Person{ 
  14.  @property 
  15.  name: string; 
  16.  constructor() { 
  17.    this.name = 'huihui'
  18.  } 
  19.  
  20.  @method 
  21.  say(){ 
  22.    return 'instance method'
  23.  } 
  24.  
  25.  @method 
  26.  static run(){ 
  27.    return 'static method'
  28.  } 
  29.  
  30. const xmz = new Person(); 
  31.  
  32. // 修改实例方法say 
  33. xmz.say = function() { 
  34.  return 'edit' 

输出如下图所示:

参数装饰

接收3个参数,分别是:

  • target :当前对象的原型
  • propertyKey :参数的名称
  • index:参数数组中的位置
  1. function logParameter(target: Object, propertyName: string, index: number) { 
  2.   console.log(target); 
  3.   console.log(propertyName); 
  4.   console.log(index); 
  5.  
  6. class Employee { 
  7.   greet(@logParameter message: string): string { 
  8.       return `hello ${message}`; 
  9.   } 
  10. const emp = new Employee(); 
  11. emp.greet('hello'); 

输入如下图:

访问器装饰

使用起来方式与方法装饰一致,如下:

  1. function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { 
  2.   console.log(target); 
  3.   console.log("prop " + propertyKey); 
  4.   console.log("desc " + JSON.stringify(descriptor) + "\n\n"); 
  5. }; 
  6.  
  7. class Person{ 
  8.  _name: string; 
  9.  constructor() { 
  10.    this._name = 'huihui'
  11.  } 
  12.  
  13.  @modification 
  14.  get name() { 
  15.    return this._name 
  16.  } 

装饰器工厂

如果想要传递参数,使装饰器变成类似工厂函数,只需要在装饰器函数内部再函数一个函数即可,如下:

  1. function addAge(age: number) { 
  2.   return function(constructor: Function) { 
  3.     constructor.prototype.age = age 
  4.   } 
  5.  
  6. @addAge(10) 
  7. class Person{ 
  8.   name: string; 
  9.   age!: number; 
  10.   constructor() { 
  11.     this.name = 'huihui'
  12.   } 
  13.  
  14. let person = new Person(); 

执行顺序

当多个装饰器应用于一个声明上,将由上至下依次对装饰器表达式求值,求值的结果会被当作函数,由下至上依次调用,例如如下:

  1. function f() { 
  2.     console.log("f(): evaluated"); 
  3.     return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 
  4.         console.log("f(): called"); 
  5.     } 
  6.  
  7. function g() { 
  8.     console.log("g(): evaluated"); 
  9.     return function (target, propertyKey: string, descriptor: PropertyDescriptor) { 
  10.         console.log("g(): called"); 
  11.     } 
  12.  
  13. class C { 
  14.     @f() 
  15.     @g() 
  16.     method() {} 
  17.  
  18. // 输出 
  19. f(): evaluated 
  20. g(): evaluated 
  21. g(): called 
  22. f(): called 

三、应用场景

可以看到,使用装饰器存在两个显著的优点:

代码可读性变强了,装饰器命名相当于一个注释

在不改变原有代码情况下,对原来功能进行扩展

后面的使用场景中,借助装饰器的特性,除了提高可读性之后,针对已经存在的类,可以通过装饰器的特性,在不改变原有代码情况下,对原来功能进行扩展

参考文献

https://www.tslang.cn/docs/handbook/decorators.html

 

https://juejin.cn/post/6844903876605280269#heading-5

 

责任编辑:武晓燕 来源: JS每日一题
相关推荐

2021-09-06 10:51:27

TypeScriptJavaScript

2021-09-08 07:49:34

TypeScript 泛型场景

2021-09-16 07:52:18

算法应用场景

2021-11-10 07:47:49

组合模式场景

2021-11-03 14:10:28

工厂模式场景

2021-08-16 08:33:26

git

2021-11-05 07:47:56

代理模式对象

2021-11-09 08:51:13

模式命令面试

2021-09-29 07:24:20

场景数据

2021-11-22 23:50:59

责任链模式场景

2021-11-11 16:37:05

模板模式方法

2021-09-28 07:12:09

测试路径

2021-10-09 10:25:41

排序应用场景

2021-10-08 09:59:32

冒泡排序场景

2021-10-13 18:01:33

快速排序场景

2021-11-04 06:58:32

策略模式面试

2021-05-31 10:35:34

TCPWebSocket协议

2021-10-18 07:51:39

回溯算法面试

2021-10-11 09:38:41

开源

2021-10-12 07:15:02

归并排序场景
点赞
收藏

51CTO技术栈公众号