一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情
装饰器是用代码包裹另一块代码的方法,它也被称为一种设计模式,在不修改被包裹代码的情况下扩展其功能。
尽管在TypeScript和Python等语言中广泛使用装饰器,但JavaScript装饰器支持仍处于第2阶段。但是,我们可以使用Babel和TypeScript,将装饰器和js结合使用。为了更好的理解,本文将详细讨论js中的装饰器。
js中装饰器的介绍
在JavaScript中,装饰器的语法比较特殊。它们的前缀是@符号,放在我们需要装饰的代码前面。此外,可以同时使用多个装饰器。下面的代码展示了JavaScript装饰器使用的一个简单示例。
@readonly
class Example {
@log()
exampleFunction() {}
}
如前所述,JavaScript 装饰器支持仍处于提议阶段。这也被称为TC39类装饰器方案。但是,对于JavaScript来说,装饰器的概念并不新鲜,因为高阶函数是函数装饰器的形式之一。
总的来说,JavaScript有3种类型的装饰器:
- 函数装饰器— 用另一个函数包装一个函数。
- 类装饰器- 一次应用于整个类。
- 类成员装饰器- 应用于类的成员。 目前,您不能在浏览器或node中运行类装饰器。因为它们需要transpiler支持。但是,如果我们使用的是函数装饰器,那么我们可以在任何地方运行它们。
函数装饰器
在函数装饰器中,我们可以简单地用另一个函数包装一个JavaScript函数,并将其用作装饰器。让我们看一个例子来理解它。
function multiply(x, y) {
console.log('Total : ' + (x*y));
}
现在,我们可以用另一个函数包装这个函数,在不改变原函数的情况下扩展它的功能。
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,可以像调用任何JavaScript函数一样调用它。
如您所见, wrapperFunction()通过包含一个记录器修改了multiply()函数。
自从引入高阶函数以来,JavaScript函数装饰器就一直存在。但是,我们不能对JavaScript类使用相同的方法引入装饰器。那么,让我们看看如何为JavaScript类使用装饰器。
类装饰器
装饰JavaScript类与装饰函数有点不同。如果尝试使用与函数装饰器中讨论的相同方法,则会出现如下类型错误:
function logDecorator (logger) {
return function () {
console.log("Logged at:", new Date().toLocaleString());
return logger();
}
}
class Calculator {
constructor(x, y) {
this.x = x;
this.y = y;
}
multiply() {
return (this.x * this.y);
}
}
let calculator = new Calculator(10, 10);
let decoratedCalculator = logDecorator(calculator.multiply);
decoratedCalculator ();
如果你对this关键词有很好的理解,你可以解决这个报错。但这并不是装饰JavaScript类的最简单方法。相反,我们需要遵循TC39类装饰器的提议来装饰JavaScript类。
类成员装饰器
类成员装饰器应用于类中的单个成员。这些成员可以是属性、方法、getter或setter,装饰器函数接受3个输入参数:
target-成员所在的类。name-类中成员的名称。descriptor-成员描述符。
例如,让我们看看@readonly装饰器。
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()函数中,我们将descriptor的writable属性设置为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'))时将值传递给该参数。
类装饰器
类装饰器立即应用于整个类。所以,我们所做的任何修改都会影响整个类。最重要的是,使用类装饰器所做的任何事情都需要返回一个新的构造函数来替换类构造函数。
类装饰函数只接受1个参数,即被装饰的构造函数。让我们举一个简单的例子来理解它。
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类的构造函数。
现在我们已经了解了不同类型的JavaScript装饰器以及如何使用它们了。但我们为什么需要装饰器呢?
为什么我们需要装饰器
将装饰器引入JavaScript的主要目的是在JavaScript类和类属性之间共享功能。但是,这并不是装饰器带来的唯一优势。
装饰器允许开发人员编写干净且可重用的代码。开发人员可以使用装饰器轻松地将功能增强与代码功能分开。
除此之外,装饰器语法非常简单,允许在不增加代码复杂性的情况下向类和属性添加新功能。这使得代码更易于维护和调试。
总结
在本文中,我讨论了不同类型的JavaScript装饰器以及如何使用它们。默认情况下,JavaScript支持函数装饰器。但是对于类装饰器来说,因为目前还是2级提案,我们还是需要transpiler来支持。
然而,装饰器在类和类属性之间共享功能方面发挥着重要作用。因此,我们可以预期JavaScript很快将正式支持装饰器。