TS的装饰器模式:前端开发的高级技巧

98 阅读3分钟

前言

嘿,各位前端小伙伴们!今天我们要聊一聊TypeScript中的装饰器模式。别被这个高大上的名字吓到,它其实就是一种让你的代码更优雅、更灵活的小魔法。如果你觉得自己的代码写得像一坨意大利面条,那么装饰器可能就是你需要的那根拯救面条的筷子。

什么是装饰器?

简单来说,装饰器就是一个函数,它可以用来修改类的行为。想象一下,你有一个普通的蛋糕,装饰器就像是蛋糕上的奶油、水果和巧克力酱,它们不改变蛋糕的本质,但能让蛋糕变得更美味、更有趣。

在TypeScript中,装饰器使用@符号来标记,可以应用于类、方法、访问器、属性或参数。

装饰器的类型

类装饰器

类装饰器在类声明之前被声明,用来监视、修改或替换类定义。

function logClass(constructor: Function) {
  console.log(constructor);
}

@logClass
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

这个例子中,@logClass装饰器会在控制台打印出Person类的构造函数。简单吧?但是别高兴得太早,这只是开胃菜。

方法装饰器

方法装饰器可以用来监视、修改或者替换方法定义。

function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  let originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling method: ${propertyKey}`);
    return originalMethod.apply(this, args);
  }
}

class Calculator {
  @logMethod
  add(a: number, b: number) {
    return a + b;
  }
}

let calc = new Calculator();
console.log(calc.add(1, 2)); // 输出:Calling method: add 然后是 3

这个装饰器会在方法被调用时打印一条日志。是不是感觉自己突然变成了007?每次方法被调用,你都能收到秘密情报。

属性装饰器

属性装饰器声明在一个属性声明之前,可以用来监视类中是否设置了某个名字的属性。

function configurable(value: boolean) {
  return function (target: any, propertyKey: string): void {
    let descriptor = Object.getOwnPropertyDescriptor(target, propertyKey) || {};
    descriptor.configurable = value;
    Object.defineProperty(target, propertyKey, descriptor);
  };
}

class Point {
  @configurable(false)
  x: number = 0;

  y: number = 0;
}

这个例子中,我们使用@configurable(false)来让x属性变得不可配置。这就像是给你的属性上了一把锁,别人想改都改不了,哈哈。

装饰器工厂

如果你想要一个可以接受参数的装饰器,那么你需要写一个装饰器工厂。装饰器工厂就是一个返回装饰器的函数。

function color(value: string) {
  return function (target: any) {
    // 这里是真正的装饰器
    console.log(`Color is ${value}`);
  }
}

@color("red")
class Car {}

这个例子中,color是一个装饰器工厂,它接受一个颜色参数,然后返回一个真正的装饰器。这就像是一个可以定制的魔法棒,你可以决定它变出什么颜色的魔法。

装饰器的执行顺序

当多个装饰器应用于一个声明上时,它们的求值会按照以下规则:

  1. 装饰器表达式会从上到下求值。
  2. 求值的结果会从下到上依次调用。
function first() {
  console.log("first(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  };
}

function second() {
  console.log("second(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  };
}

class ExampleClass {
  @first()
  @second()
  method() {}
}

输出结果:

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called

这就像是叠披萨,先放的配料反而在上面。所以,要好好考虑你的装饰器顺序哦!

实际应用场景

1. 性能监控

function measure(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const finish = performance.now();
    console.log(`${propertyKey} execution time: ${finish - start} milliseconds`);
    return result;
  };
  return descriptor;
}

class TaskRunner {
  @measure
  runTask() {
    // 模拟一个耗时操作
    for (let i = 0; i < 1000000; i++) {}
  }
}

new TaskRunner().runTask();

这个装饰器可以帮你监控方法的执行时间。再也不用担心哪个方法偷偷摸鱼了!

2. 自动绑定方法

function autoBind(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  let originalMethod = descriptor.value;
  return {
    configurable: true,
    get() {
      return originalMethod.bind(this);
    }
  };
}

class ClickCounter {
  count = 0;

  @autoBind
  increment() {
    this.count++;
    console.log(this.count);
  }
}

let counter = new ClickCounter();
let increment = counter.increment;
increment(); // 1
increment(); // 2

这个装饰器可以自动绑定方法到实例上,再也不用担心this丢失的问题了。这简直就是前端开发中的救命稻草啊!

结语

好了,各位前端侠客们,我们今天的TypeScript装饰器之旅就到此为止了。希望通过这篇文章,你已经掌握了这个强大的武器。记住,装饰器就像是代码世界的调味料,适量使用可以让你的代码更加美味可口。但是也别滥用哦,不然你的代码可能会变得像某些网红奶茶一样,华而不实。

最后,祝大家编码愉快,May the TypeScript be with you!

海码面试 小程序

包含最新面试经验分享,面试真题解析,全栈2000+题目库,前后端面试技术手册详解;无论您是校招还是社招面试还是想提升编程能力,都能从容面对~