TypeScript 装饰器:高级编程本领

打印 上一主题 下一主题

主题 576|帖子 576|积分 1728




  

前言

TypeScript 装饰器 | 阮一峰 TypeScript 教程


  • 迩来,在进行学习 Nest 后台服务的时候发现,对于 TS 装饰器的用法还是有一些暗昧,于是写一篇文章来详细解说这个概念,目前设置到的知识点如下:

    • 类、原型与原型链、this 指向与绑定

前置知识

类与实例

TypeScript 的 class 类型 | 阮一峰 TypeScript 教程
原型与原型链



  • 原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是 prototype 对象,主要作用是共享方法
  • 原型链:由相互关联的原型构成的链状结构就是原型链



  • 以一个经典的类继承举例
  • 起首定义一个 Person 类,定义 Teacher 继承 Person 类,然后输出参数构造 Teacher 类的实例 t1
  • 则原型链向上查找的过程为 t1 的隐式原型 __proto__ 指向类 Teacher 的显示原型 prototype,以此类推实行父类 Person 的 prototype,父类的 __proto__ 指向 Object 的 prototype。最终 Object 的 __proto__ 指向 null
  • instanceof:能否在向上查找的原型链中,找到和目标匹配的显示原型
  • hasOwnProperty() 方法返回一个布尔值,表现对象自有属性(而不是继承来的属性)中是否具有指定的属性。
this 指向与绑定

一文搞懂 this 指向-CSDN博客
雷同点:都会改变函数的this指向
绑定不同:


  • bind不会改变原函数的this关键字,返回一个新的函数,不会立刻调用
  • call、apply都可以立刻调用原函数
传参不同:


  • bind、call传入一组参数
  • apply传入数组参数
简介


  • 装饰器(Decorator):是一种语法结构,在定义时修改类的行为,表现 @ + 表达式

    • 表达式必须是一个函数,或者实行后得到一个函数
    • 吸收所装饰对象的一些相关值作为参数
    • 函数要么修改原有值,要么返回新对象代替修饰的目标对象

  • TypeScript 早期支持旧的装饰器语法(后续解说),5.0 后使用尺度语法,传统语法需要打开--experimentalDecorators编译参数
  • 不同类型的装饰器可以修改/代替原目标对象的内容
  • 结构如下所示:
  1. type Decorator = (
  2.   value: DecoratedValue, // 所装饰的对象
  3.   context: {   // 上下文对象
  4.     kind: string; // 类型,class, method, getter, setter, field, accessor
  5.     name: string | symbol; // 所装饰目标的名称
  6.     addInitializer?(initializer: () => void): void; // 完善类的初始化逻辑
  7.     static?: boolean;   // 所装饰的对象是否为类的私有成员
  8.     private?: boolean;  // 所装饰的对象是否为类的静态成员
  9.     access: {           // 一个对象,包含了某个值的 get 和 set 方法
  10.       get?(): unknown;
  11.       set?(value: unknown): void;
  12.     };
  13.   }
  14. ) => void | ReplacementValue;
复制代码
类装饰器

结构

  1. type ClassDecorator = (
  2.   value: Function, // 当前类本身
  3.   context: {
  4.     kind: "class";
  5.     name: string | undefined;
  6.     addInitializer(initializer: () => void): void;
  7.   }
  8. ) => Function | void;
复制代码
案例

  1. function Greeter(value: any, context: any) {
  2.   // 代替当前的类
  3.   return class extends value {
  4.     age: number = 18
  5.     greet() {
  6.       console.log('Hello, ' + this.name, this.age)
  7.     }
  8.   }
  9.   // 代替当前的构造方法
  10.   const newConstructor = function (...args: any[]) {
  11.     const instance = new value(...args)
  12.     instance.age = 18
  13.     // 添加新的方法
  14.     instance.greet = function () {
  15.       console.log('Hello, ' + this.name, this.age)
  16.     }
  17.     return instance
  18.   }
  19.   // 复制原型链
  20.   newConstructor.prototype = value.prototype
  21.   return newConstructor
  22. }
  23. // @ts-ignore
  24. @Greeter
  25. class Person {
  26.   public name: string
  27.   constructor(name: string) {
  28.     this.name = name
  29.   }
  30.   greet() {}
  31. }
  32. const person = new Person('John')
  33. person.greet() // Hello, John
复制代码
方法装饰器

结构

  1. // 方法装饰器会改写类的原始方法,实际上就是传递方法的代码结构,在基础上进行修改
  2. type ClassMethodDecorator = (
  3.   value: Function, // 方法体
  4.   context: {  
  5.     kind: "method";
  6.     name: string | symbol;
  7.     static: boolean;   // 是否为静态方法
  8.     private: boolean;  // 是否为只读属性
  9.     access: { get: () => unknown };   // 方法的存取器
  10.     addInitializer(initializer: () => void): void;   
  11.   }
  12. ) => Function | void;
复制代码
案例

  1. function delay(ms: number) {
  2.   return function (target: any, context: any) {
  3.     return function (...args: any[]) {
  4.       console.log('延时执行', ms)
  5.       setTimeout(() => {
  6.         target.apply(target, args)
  7.         console.log('延时结束')
  8.       }, ms)
  9.     }
  10.   }
  11. }
  12. class Person {
  13.   public name: string
  14.   constructor(name: string) {
  15.     this.name = name
  16.   }
  17.   @delay(1000)
  18.   greet() {
  19.     console.log(this.name)
  20.   }
  21. }
复制代码
属性装饰器

结构

  1. type ClassFieldDecorator = (
  2.   value: undefined,   // 不能通过 value 获取装饰器的值
  3.   context: {
  4.     kind: "field";
  5.     name: string | symbol;
  6.     static: boolean;
  7.     private: boolean;
  8.     access: { get: () => unknown; set: (value: unknown) => void };
  9.     addInitializer(initializer: () => void): void;
  10.   }
  11. ) => (initialValue: unknown) => unknown | void;
复制代码
案例

  1. function fieldDecorator(target: any, context: any) {
  2.   const { kind, name } = context
  3.   if (kind === 'field') {
  4.     return function (value: string) {
  5.       console.log(`Decorating ${name} with value ${value}`)
  6.       return value
  7.     }
  8.   }
  9. }
  10. function twice(target: any, context: any) {
  11.   return (value: number) => value * 2
  12. }
  13. class Person {
  14.   @fieldDecorator
  15.   public name: string = 'John Doe'
  16.   @twice
  17.   public age: number = 30
  18. }
  19. const person = new Person()
  20. console.log(person.name)
  21. console.log(person.age)
复制代码
getter、setter 装饰器

结构

  1. type ClassGetterDecorator = (
  2.   value: Function,
  3.   context: {
  4.     kind: "getter";
  5.     name: string | symbol;
  6.     static: boolean;
  7.     private: boolean;
  8.     access: { get: () => unknown };
  9.     addInitializer(initializer: () => void): void;
  10.   }
  11. ) => Function | void;
  12. type ClassSetterDecorator = (
  13.   value: Function,
  14.   context: {
  15.     kind: "setter";
  16.     name: string | symbol;
  17.     static: boolean;
  18.     private: boolean;
  19.     access: { set: (value: unknown) => void };
  20.     addInitializer(initializer: () => void): void;
  21.   }
  22. ) => Function | void;
复制代码
案例

  1. function lazy(target: any, { kind, name }: any) {
  2.   if (kind === 'getter') {
  3.     // 返回函数,这个函数会在实例上定义一个与装饰器目标同名的属性
  4.     return function (this: any) {
  5.       const result = target.call(this)
  6.       Object.defineProperty(this, name, {
  7.         value: result,
  8.         writable: false,
  9.       })
  10.       return result
  11.     }
  12.   }
  13. }
  14. class C {
  15.   @lazy
  16.   get value() {
  17.     console.log('正在计算……')
  18.     return '开销大的计算结果'
  19.   }
  20. }
  21. const inst = new C()
  22. console.log(inst.value) // 正在计算…… 开销大的计算结果
  23. console.log(inst.value) // 开销大的计算结果
复制代码
accessor 装饰器

结构

  1. type ClassAutoAccessorDecorator = (
  2.   value: {   // 将变量变为私有属性,并自动添加 get/set 方法,静态属性和私有属性都可用
  3.     get: () => unknown;
  4.     set: (value: unknown) => void;
  5.   },
  6.   context: {
  7.     kind: "accessor";
  8.     name: string | symbol;
  9.     access: { get(): unknown; set(value: unknown): void };
  10.     static: boolean;
  11.     private: boolean;
  12.     addInitializer(initializer: () => void): void;
  13.   }
  14. ) => {
  15.   get?: () => unknown;
  16.   set?: (value: unknown) => void;
  17.   init?: (initialValue: unknown) => unknown; // 可以取代初始变量的值
  18. } | void;
复制代码
案例

  1. function logAccess(value: any, context: any) {
  2.   const { get, set } = value
  3.   return {
  4.     get() {
  5.       const result = get.call(this)
  6.       console.log(`Getting ${String(context.name)}: ${result}`)
  7.       return result
  8.     },
  9.     set(newValue: number) {
  10.       console.log(`Setting ${String(context.name)} to ${newValue}`)
  11.       set.call(this, newValue)
  12.     },
  13.     init(initValue: number) {
  14.       console.log(`Initializing ${String(context.name)} with ${initValue}`)
  15.       return initValue + 1 // 返回初始化值
  16.     },
  17.   }
  18. }
  19. class Student {
  20.   @logAccess
  21.   accessor age: number = 0
  22. }
  23. const student = new Student()
  24. console.log(student.age) // Getting age: 1
  25. student.age = 20 // Setting age to 20;
复制代码
装饰器实行顺序

  1. function d(str: string) {
  2.   console.log(`评估 @d(): ${str}`)
  3.   return (value: any, context: any) => console.log(`应用 @d(): ${str}`)
  4. }
  5. function log(str: string) {
  6.   console.log(str + ' 被调用')
  7.   return str
  8. }
  9. @d('类装饰器')
  10. class T {
  11.   @d('静态属性装饰器')
  12.   static staticField = log('静态属性值')
  13.   @d('实例属性装饰器')
  14.   instanceField = log('实例属性值')
  15.   @d('静态方法装饰器')
  16.   static staticMethod() {
  17.     return log('静态方法')
  18.   }
  19.   @d('实例方法装饰器 2')
  20.   @d('实例方法装饰器 1')
  21.   [log('instanceMethod')]() {
  22.     return log('实例方法')
  23.   }
  24.   @d('getter 装饰器')
  25.   get getter() {
  26.     return log('getter')
  27.   }
  28.   @d('setter 装饰器')
  29.   set setter(value: any) {
  30.     log('setter')
  31.   }
  32. }
  33. const t = new T()
  34. console.log(t.instanceField)
  35. // 阶段一:评估
  36. 评估 @d(): 类装饰器
  37. 评估 @d(): 静态属性装饰器
  38. 评估 @d(): 实例属性装饰器
  39. 评估 @d(): 静态方法装饰器
  40. 评估 @d(): 实例方法装饰器 2
  41. 评估 @d(): 实例方法装饰器 1
  42. instanceMethod 被调用
  43. 评估 @d(): getter 装饰器
  44. 评估 @d(): setter 装饰器
  45. // 阶段二:应用
  46. 应用 @d(): 静态方法装饰器
  47. 应用 @d(): 实例方法装饰器 1
  48. 应用 @d(): 实例方法装饰器 2
  49. 应用 @d(): getter 装饰器
  50. 应用 @d(): setter 装饰器
  51. 应用 @d(): 静态属性装饰器
  52. 应用 @d(): 实例属性装饰器
  53. 应用 @d(): 类装饰器
  54. 静态属性值 被调用
  55. // 阶段三:实例执行
  56. 实例属性值 被调用
  57. 实例属性值
复制代码

  • 评估阶段(evaluation):计算 @ 符号反面表达式的值,确保是函数

    • 评估阶段的实行,从上到小实行
    • 碰到目标是函数的返回的属性,需要实行返回结果才行

  • 应用阶段(application):评估之后得到的函数,应用到所装饰目标

    • 多个装饰器的,从下到上依次实行
    • 静态方法 => 实例方法 => getter => setter => 静态属性 => 实例属性 => 类装饰器
    • 静态属性值由函数返回的,此时实行
    • 实例属性值由函数返回的,实例调用才实行


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦应逍遥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表