装饰器是一种包装代码的简单方法,它也是一种设计模式,能够扩展包装代码的功能而不修改它。
尽管装饰器在 TypeScript 和 Python 等语言中被广泛使用,但是 JavaScript 装饰器的支持仍处于第 2 阶段提案中(stage 2 proposal)。但是,我们可以借助 Babel 和 TypeScript 编译器使用 JavaScript 装饰器。
本文将详细讨论 JavaScript 装饰器,提升理解。冲~
简介
在 JavaScript 中,装饰器有一种特殊的语法。它们以 @ 符号为前缀,放置在我们需要装饰的代码之前。另外,可以一次使用多个装饰器。
代码示例:
@readonly
class Example {
@log()
exampleFunction() {}
}
复制代码
前面已经提到,JavaScript 装饰器支持仍处于提案阶段。但是,装饰器的概念对 JavaScript 来说并不新鲜,因为高阶函数是函数装饰器的另一种形式。
总的来说,我们可以在 JavaScript 中分出 3 种类型的装饰器:
- 函数装饰器——用函数来包装函数。
- 类装饰器—— 一次应用于整个类。
- 类成员装饰器——应用于类的成员
目前,不能在浏览器或 Node.js 环境中运行类装饰器,因为它们需要转译器支持。但是,如果使用函数式装饰器,则可以在任何地方运行它们。
函数装饰器
我们可以尝试用一个函数包装另一个函数,来扩展功能而不改变原始函数。
function multiply(x, y) {
console.log('Total : ' + (x*y));
}
function logDecorator(logger) {
return function (message) {
const result = logger.apply(this, arguments);
console.log("Logged at:", new Date().toLocaleString());
return result;
}
}
const wrapperFunction = logDecorator(multiply);
wrapperFunction(10,10)
复制代码
在上面的例子中,wrapperFunction() 通过 logDecorator() 修改了 multiply() 函数,它可以像任何 JavaScript 函数一样被调用。
自从引入了高阶函数以来,JavaScript 函数装饰器就一直存在。但是,我们不能对 JavaScript 的类使用相同的方法。
类装饰器
类装饰器有点不同。如果尝试使用相同的方法,将会报错 TypeError:
如果你对 JavaScript this 关键字理解透彻,可以克服这个问题。但这不是最简单方法。
类装饰器应用于整个类。因此,我们所做的任何修改都会影响整个类。对类装饰器所做的任何事情都需要通过返回一个新的构造函数来替换类构造函数。
举个例子:
function log(name) {
return function decorator(Class) {
return (...args) => {
console.log(`Arguments for ${name}: ${args}`);
return new Class(...args);
};
}
}
@log('Multiply')
class Calculator {
constructor (x,y) { }
}
calculator = new Calculator(10,10);
// Arguments for Multiply: [10, 10]
console.log(calculator);
// Calculator {}
复制代码
log() 函数接受 Calculator 类作为参数,并返回一个新函数来替换 Calculator 类的构造函数;
类成员装饰器
类成员装饰器应用于类中的单个成员。这些成员可以是属性、方法、getter 或 setter,装饰器函数接受 3 个输入参数:
- Target - 成员所在的类。
- Name - 类成员的名称。
- descriptor - 成员描述符。
代码示例:
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Example {
x() {}
@readonly
y() {}
}
const myClass = new Example();
myClass.x = 10;
myClass.y = 20;
复制代码
在 readonly() 函数中,我们将描述符的可写属性设置为 false。然后,将它用作函数 y() 的装饰器。如果尝试修改它,会得到一个 TypeError。
此外,我们还可以创建自定义装饰器,并将它们用作类成员装饰器,如下:
function log(name) {
return function decorator(t, n, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function (...args) {
console.log("Logged at:", new Date().toLocaleString());
try {
const result = original.apply(this, args);
console.log(`Result from ${name}: ${result}`);
return result;
} cach (e) {
console.log(`Error from $ {name}: ${e}`);
thro e;
}
};
}
return descriptor;
};
}
class Calculator {
@log('Multiply')
multiply(x,y){
return x*y;
}
}
calculator = new Calculator();
calculator.multiply(10,10);
// Logged at: 1/12/2022, 08:00:00 PM
// Result from Multiply: 100
复制代码
Calculator 类有一个名为 multiply() 的方法,并使用 log() 函数对其进行修饰。Log() 函数接受单个参数作为输入,当我们调用装饰器 (@log('Multiply')) 时,我们可以将值传递给该参数。
小结
将装饰器引入 JavaScript 的主要目的是在 JavaScript 类和类属性之间共享功能。
但是,这并不是装饰器带来的唯一优势。
装饰器允许开发人员编写干净且可重用的代码。开发人员可以使用装饰器轻松地将功能的增强与代的码特性分开。
除此之外,装饰器语法非常简单,允许在不增加代码复杂性的情况下向类和属性添加新功能。
这使得代码更易于维护和调试。