TypeScript 装饰器:高级编程技巧

在这里插入图片描述



前言

TypeScript 装饰器 | 阮一峰 TypeScript 教程

  • 最近,在进行学习 Nest 后台服务的时候发现,对于 TS 装饰器的用法还是有一些模糊,于是写一篇文章来详细讲解这个概念,目前设置到的知识点如下:
    • 类、原型与原型链、this 指向与绑定

前置知识

类与实例

TypeScript 的 class 类型 | 阮一峰 TypeScript 教程

原型与原型链

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

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

this 指向与绑定

一文搞懂 this 指向-CSDN博客

相同点:都会改变函数的this指向

绑定不同:

  • bind不会改变原函数的this关键字,返回一个新的函数,不会立刻调用
  • callapply都可以立即调用原函数

传参不同:

  • bindcall传入一组参数
  • apply传入数组参数

简介

  1. 装饰器(Decorator):是一种语法结构,在定义时修改类的行为,表现 @ + 表达式
    1. 表达式必须是一个函数,或者执行后得到一个函数
    2. 接收所装饰对象的一些相关值作为参数
    3. 函数要么修改原有值,要么返回新对象取代修饰的目标对象
  2. TypeScript 早期支持旧的装饰器语法(后续讲解),5.0 后使用标准语法,传统语法需要打开--experimentalDecorators编译参数
  3. 不同类型的装饰器可以修改/代替原目标对象的内容
  4. 结构如下所示:
type Decorator = (
  value: DecoratedValue, // 所装饰的对象
  context: {   // 上下文对象
    kind: string; // 类型,class, method, getter, setter, field, accessor
    name: string | symbol; // 所装饰目标的名称 
    addInitializer?(initializer: () => void): void; // 完善类的初始化逻辑
    static?: boolean;   // 所装饰的对象是否为类的私有成员
    private?: boolean;  // 所装饰的对象是否为类的静态成员
    access: {           // 一个对象,包含了某个值的 get 和 set 方法
      get?(): unknown;
      set?(value: unknown): void;
    };
  }
) => void | ReplacementValue;

类装饰器

结构

type ClassDecorator = (
  value: Function, // 当前类本身
  context: {
    kind: "class";
    name: string | undefined;
    addInitializer(initializer: () => void): void;
  }
) => Function | void;

案例

function Greeter(value: any, context: any) {
  // 代替当前的类
  return class extends value {
    age: number = 18

    greet() {
      console.log('Hello, ' + this.name, this.age)
    }
  }

  // 代替当前的构造方法
  const newConstructor = function (...args: any[]) {
    const instance = new value(...args)
    instance.age = 18

    // 添加新的方法
    instance.greet = function () {
      console.log('Hello, ' + this.name, this.age)
    }

    return instance
  }

  // 复制原型链
  newConstructor.prototype = value.prototype

  return newConstructor
}

// @ts-ignore
@Greeter
class Person {
  public name: string

  constructor(name: string) {
    this.name = name
  }

  greet() {}
}

const person = new Person('John')
person.greet() // Hello, John

方法装饰器

结构

// 方法装饰器会改写类的原始方法,实际上就是传递方法的代码结构,在基础上进行修改

type ClassMethodDecorator = (
  value: Function, // 方法体
  context: {  
    kind: "method";
    name: string | symbol;
    static: boolean;   // 是否为静态方法
    private: boolean;  // 是否为只读属性
    access: { get: () => unknown };   // 方法的存取器
    addInitializer(initializer: () => void): void;   
  }
) => Function | void;

案例

function delay(ms: number) {
  return function (target: any, context: any) {
    return function (...args: any[]) {
      console.log('延时执行', ms)

      setTimeout(() => {
        target.apply(target, args)
        console.log('延时结束')
      }, ms)
    }
  }
}

class Person {
  public name: string

  constructor(name: string) {
    this.name = name
  }

  @delay(1000)
  greet() {
    console.log(this.name)
  }
}

属性装饰器

结构

type ClassFieldDecorator = (
  value: undefined,   // 不能通过 value 获取装饰器的值
  context: {
    kind: "field";
    name: string | symbol;
    static: boolean;
    private: boolean;
    access: { get: () => unknown; set: (value: unknown) => void };
    addInitializer(initializer: () => void): void;
  }
) => (initialValue: unknown) => unknown | void;

案例

function fieldDecorator(target: any, context: any) {
  const { kind, name } = context
  if (kind === 'field') {
    return function (value: string) {
      console.log(`Decorating ${name} with value ${value}`)

      return value
    }
  }
}

function twice(target: any, context: any) {
  return (value: number) => value * 2
}

class Person {
  @fieldDecorator
  public name: string = 'John Doe'

  @twice
  public age: number = 30
}

const person = new Person()
console.log(person.name)
console.log(person.age)

getter、setter 装饰器

结构

type ClassGetterDecorator = (
  value: Function,
  context: {
    kind: "getter";
    name: string | symbol;
    static: boolean;
    private: boolean;
    access: { get: () => unknown };
    addInitializer(initializer: () => void): void;
  }
) => Function | void;

type ClassSetterDecorator = (
  value: Function,
  context: {
    kind: "setter";
    name: string | symbol;
    static: boolean;
    private: boolean;
    access: { set: (value: unknown) => void };
    addInitializer(initializer: () => void): void;
  }
) => Function | void;

案例

function lazy(target: any, { kind, name }: any) {
  if (kind === 'getter') {
    // 返回函数,这个函数会在实例上定义一个与装饰器目标同名的属性
    return function (this: any) {
      const result = target.call(this)
      Object.defineProperty(this, name, {
        value: result,
        writable: false,
      })
      return result
    }
  }
}

class C {
  @lazy
  get value() {
    console.log('正在计算……')
    return '开销大的计算结果'
  }
}

const inst = new C()
console.log(inst.value) // 正在计算…… 开销大的计算结果
console.log(inst.value) // 开销大的计算结果

accessor 装饰器

结构

type ClassAutoAccessorDecorator = (
  value: {   // 将变量变为私有属性,并自动添加 get/set 方法,静态属性和私有属性都可用
    get: () => unknown;
    set: (value: unknown) => void;
  },
  context: {
    kind: "accessor";
    name: string | symbol;
    access: { get(): unknown; set(value: unknown): void };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
  }
) => {
  get?: () => unknown;
  set?: (value: unknown) => void;
  init?: (initialValue: unknown) => unknown; // 可以取代初始变量的值
} | void;

案例

function logAccess(value: any, context: any) {
  const { get, set } = value

  return {
    get() {
      const result = get.call(this)
      console.log(`Getting ${String(context.name)}: ${result}`)
      return result
    },
    set(newValue: number) {
      console.log(`Setting ${String(context.name)} to ${newValue}`)
      set.call(this, newValue)
    },
    init(initValue: number) {
      console.log(`Initializing ${String(context.name)} with ${initValue}`)
      return initValue + 1 // 返回初始化值
    },
  }
}

class Student {
  @logAccess
  accessor age: number = 0
}

const student = new Student()
console.log(student.age) // Getting age: 1
student.age = 20 // Setting age to 20;

装饰器执行顺序

function d(str: string) {
  console.log(`评估 @d(): ${str}`)
  return (value: any, context: any) => console.log(`应用 @d(): ${str}`)
}

function log(str: string) {
  console.log(str + ' 被调用')
  return str
}

@d('类装饰器')
class T {
  @d('静态属性装饰器')
  static staticField = log('静态属性值')

  @d('实例属性装饰器')
  instanceField = log('实例属性值')

  @d('静态方法装饰器')
  static staticMethod() {
    return log('静态方法')
  }

  @d('实例方法装饰器 2')
  @d('实例方法装饰器 1')
  [log('instanceMethod')]() {
    return log('实例方法')
  }

  @d('getter 装饰器')
  get getter() {
    return log('getter')
  }

  @d('setter 装饰器')
  set setter(value: any) {
    log('setter')
  }
}

const t = new T()
console.log(t.instanceField)
// 阶段一:评估
评估 @d(): 类装饰器
评估 @d(): 静态属性装饰器
评估 @d(): 实例属性装饰器
评估 @d(): 静态方法装饰器
评估 @d(): 实例方法装饰器 2
评估 @d(): 实例方法装饰器 1

instanceMethod 被调用

评估 @d(): getter 装饰器
评估 @d(): setter 装饰器

// 阶段二:应用
应用 @d(): 静态方法装饰器
应用 @d(): 实例方法装饰器 1
应用 @d(): 实例方法装饰器 2
应用 @d(): getter 装饰器
应用 @d(): setter 装饰器
应用 @d(): 静态属性装饰器
应用 @d(): 实例属性装饰器
应用 @d(): 类装饰器
静态属性值 被调用

// 阶段三:实例执行
实例属性值 被调用
实例属性值
  1. 评估阶段(evaluation):计算 @ 符号后面表达式的值,确保是函数
    1. 评估阶段的执行,从上到小执行
    2. 遇到目标是函数的返回的属性,需要执行返回结果才行
  2. 应用阶段(application):评估之后得到的函数,应用到所装饰目标
    1. 多个装饰器的,从下到上依次执行
    2. 静态方法 => 实例方法 => getter => setter => 静态属性 => 实例属性 => 类装饰器
    3. 静态属性值由函数返回的,此时执行
    4. 实例属性值由函数返回的,实例调用才执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知心宝贝

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值