灏天阁

JavaScript 装饰器:一个高端玩具还是一个必备工具?

· Yin灏

在 JavaScript 中,装饰器是一种能够装饰函数和类的语言特性。通过装饰器,我们可以为函数和类添加各种有用的功能,比如日志记录、输入验证、结果缓存、错误处理等,而无需修改原始函数或类的代码。本文将介绍如何使用装饰器,并提供一些示例和使用场景。

如何使用装饰器

在 JavaScript 中,装饰器是一种基于元编程的语言特性,可以在运行时修改函数或类的行为。装饰器是通过在函数或类声明前添加 @ 符号来使用的,比如:

@decorator function foo() {}

或者

@decorator
class Bar {}

装饰器函数是一个普通函数,它接收三个参数:

  • target:被装饰的函数或类。
  • name:被装饰的方法名或类名。
  • descriptor:被装饰方法的描述符。

装饰器函数可以修改 descriptor.value 来修改方法的实现,或者返回一个新的描述符来改变方法的行为。

下面是一个简单的示例,使用装饰器为 sum 函数添加日志记录的功能:

function log(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    console.log(`[${name}] called with args:`, args);
    const result = original.apply(this, args);
    console.log(`[${name}] returned:`, result);
    return result;
  };
  return descriptor;
}

class Calculator {
  @log
  static sum(a, b) {
    return a + b;
  }
}

console.log(Calculator.sum(1, 2)); // [sum] called with args: [1, 2]
// [sum] returned: 3
// 3

在上面的示例中,我们定义了一个 log 装饰器函数,它接收三个参数 targetnamedescriptor。在 descriptor.value 中,我们将原始的 sum 函数替换为一个新的函数,新函数在执行 sum 前后都会打印日志。在 Calculator 类中,我们使用 @log 装饰器将 sum 函数装饰起来,使其具有日志记录的功能。当我们调用 Calculator.sum(1, 2) 时,将会打印出日志记录。

装饰器的使用场景

装饰器可以为函数和类添加各种有用的功能,下面是一些常见的使用场景。

1. 日志记录

日志记录是一个常见的需求,可以使用装饰器轻松地为函数和类添加日志记录功能,以便调试和错误处理。

function log(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    console.log(`[${name}] called with args:`, args);
    const result = original.apply(this, args);
    console.log(`[${name}] returned:`, result);
    return result;
  };
  return descriptor;
}

class Calculator {
  @log
  static sum(a, b) {
    return a + b;
  }
}

console.log(Calculator.sum(1, 2)); // [sum] called with args: [1, 2]
// [sum] returned: 3
// 3

在上面的示例中,我们定义了一个 log 装饰器函数,它接收三个参数 targetnamedescriptor。在 descriptor.value 中,我们将原始的 sum 函数替换为一个新的函数,新函数在执行 sum 前后都会打印日志。在 Calculator 类中,我们使用 @log 装饰器将 sum 函数装饰起来,使其具有日志记录的功能。当我们调用 Calculator.sum(1, 2) 时,将会打印出日志记录。

2. 输入验证

输入验证是一个重要的安全需求,可以使用装饰器轻松地为函数和类添加输入验证功能,以便确保输入参数的有效性。

function validateInputs(target, name, descriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args) {
    if (args.some((arg) => typeof arg !== "number")) {
      throw new Error(`[${name}] only accepts numbers as inputs`);
    }
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculator {
  @validateInputs
  static sum(a, b) {
    return a + b;
  }
}

console.log(Calculator.sum(1, 2)); // 3
console.log(Calculator.sum(1, "2")); // Error: [sum] only accepts numbers as inputs

在上面的示例中,我们定义了一个 validateInputs 装饰器函数,它接收三个参数 targetnamedescriptor。在 descriptor.value 中,我们将原始的 sum 函数替换为一个新的函数,新函数在执行 sum 前会验证输入参数是否为数字类型。如果输入参数不是数字类型,将会抛出一个错误。在 Calculator 类中,我们使用 @validateInputs 装饰器将 sum 函数装饰起来,使其具有输入验证的功能。当我们调用 Calculator.sum(1, "2")

总结

装饰器是一种非常有用的语言特性,可以为函数和类添加各种有用的功能,比如日志记录、输入验证、性能优化等。装饰器是通过在函数和类定义前使用 @ 符号来应用的。装饰器本质上是一个高阶函数,它接收目标对象作为参数,并返回一个新的对象,该对象包含被装饰的函数或类。

装饰器可以帮助我们更好地组织代码,并且使代码更加可读、可维护和可扩展。

用装饰器函数实现一个小功能

最后玩一个练习吧,实现一个回文字符串的功能吧,看看我们是否真的学会了

创建一个装饰器函数,将其应用到一个类的方法上。该方法接收一个字符串参数,需要检查该参数是否是回文字符串。如果是回文字符串,则返回原始字符串,否则返回该字符串的反转字符串。

- Book Lists -