Skip to content

类装饰器

基本语法

类装饰器时一个应用在类声明上的函数, 可以为类添加额外的功能, 或添加额外的逻辑

ts
/**
 * Demo 函数会在 Person 类定义时执行
 * 参数说明: target 参数是被修饰的类, 即 Person
 */
function Demo(target: Function) {
  console.log(target)
}

@Demo
class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

应用示例

需求: 定义一个装饰器, 实现Person实例调用toString时返回JSON.stringify的执行结果

ts
function CustomString(target: Function) {
  target.prototype.toString = function() {
    return JSON.stringify(this)
  }
  // 封锁
  Object.seal(target)
}

@CustomString
class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

const man = new Person('张三', 18)
// console.log(man.toString()) // [object Object]
// console.log(JSON.stringify(man)) // "{"name": "张三", "age": 18}"

console.log(man.toString()) // "{"name": "张三", "age": 18}"

interface Person {
  x: number
}
// 封锁后不能向原型上添加属性
Person.prototype.x = 99

关于返回值

  • 类装饰器有返回值: 若类装饰器返回一个新的类, 那这个新类将替换掉被装饰的类
  • 类装饰器无返回值: 若类装饰器无返回值或者返回undefined, 那被装饰的类不会被替换
ts
function Demo(target: Function) {
  // 装饰器有返回值时, 该返回值会替换掉被装饰的类
  return class {
    test() {
      console.log(200)
      console.log(300)
    }
  }
}

@Demo
class Person {
  test() {
    console.log(100)
  }
}

console.log(Person)

关于构造类型

TS中, Function类型所表示的范围非常广泛, 包括: 普通函数, 箭头函数, 方法等等

但并非所有Function类型的函数都可以被new关键字实例化, 如箭头函数是不能被实例化的

那么TS中如何声明一个构造类型呢?

  • 仅声明构造类型
ts
/**
 * new 表示: 该类型是可以使用 new 关键字实例化
 * ...args 表示: 构造器可以接受[任意数量]的参数
 * any[] 表示: 构造器可以接受[任意类型]的参数
 * {} 表示: 返回类型是对象, 非 null, 非 undefined 的对象
 */
// 定义 Constructor 类型, 其含义是构造类型
type Constructor = new (...args: any[]) => {}

function test(fn: Constructor) {}

class Person{}

test(Person)
test(() => {}) // 警告: 类型“() => void”提供的内容与签名“new (...args: any[]): {}”不匹配
  • 声明构造类型并指定静态属性
ts
// 定义一个构造器,并且包含一个静态属性 wife
type ConstructorStatic = {
  new (...args: any[]): {} // 构造签名
  wife: string // wife 静态属性
}

function demo(fn: ConstructorStatic) {}

class Person {
  static wife = 'Person'
}

demo(Person)

替换被装饰的类

对于高级一些的装饰器, 不仅仅是覆盖一个原型上的方法, 还要有更多功能, 例如添加新的方法和状态

需求: 设计一个LogTime装饰器, 可以给实例添加一个属性, 用于记录实例对象的创建时间, 再添加一个方法用于读取创建时间

ts
// 定义 Constructor 类型, 其含义是构造类型
type Constructor = new (...args: any[]) => {}

interface User {
  getTime(): string
}

function LogTime<T extends Constructor>(target: T) {
  return class extends target {
    createTime: Date;
    constructor(...args: any[]) {
      super(...args)
      this.createTime = new Date()
    }
    getTime() {
      return `该对象创建时间为: ${this.createTime}`
    }
  }
}

@LogTime
class User {
  constructor(
    public name: string,
    public age: number
  ) {}
  speak() {
    console.log(`我是${this.name}, 今年${this.age}岁`)
  }
}

const man = new User('张三', 17)
console.log(man.getTime())