装饰器的简单说明
装饰器的作用主要是将函数包装到另外一个函数中,这样就可以使用装饰器来扩展某个类而不影响某个类的代码。
使用闭包来实现装饰器
在了解装饰器的作用后,我们可以编写一个用于计算函数调用次数的闭包装饰器来体验一下装饰器的作用。具体的代码如下:
// 计算参数的和
let sum = (...args) => {
return [...args].reduce((acc, num) => acc + num);
};
// 测试函数
let fn1 = () => {
console.log(123);
};
// 计算函数被调用的次数
const callCounter = (fn) => {
let count = 0;
return (...args) => {
console.log(`函数执行了${(count += 1)}次`);
return fn(...args);
};
};
sum = callCounter(sum); // 使用callCounter函数来装饰sum函数
fn1 = callCounter(fn1); // 使用callCounter函数来装饰fn1函数
console.log(sum(1, 2, 3));
console.log(sum(33, 11));
console.log(sum(33));
fn1();
fn1();
fn1();
fn1();
在上述的例子中,我们可以看到callCounter
是一个闭包函数,它主要是声明了一个计数器并且接受参数为函数,这样我们就可以在闭包内部来执行接收的函数,并且可以记住函数的调用次数。
闭包实现装饰器第二个例子
// 计算矩形面积
let getArea = (width, height) => {
return width * height;
};
// 检测函数的个数
const countParams = (fn) => {
const name = fn.name;
return (...params) => {
if (params.length !== fn.length) {
throw new Error(`函数 ${name} 的参数个数不正确`);
}
return fn(...params);
};
};
// 检测函数的参数是否为正数
const requireIntegers = (fn) => {
const name = fn.name;
return (...params) => {
params.forEach((param) => {
if (!Number.isInteger(param)) {
throw new TypeError(`函数 ${name} 的参数必须是整数`);
}
});
return fn(...params);
};
};
getArea = countParams(getArea);
getArea = requireIntegers(getArea);
console.log(getArea(10, 11));
console.log(getArea(1, 2, 3));
console.log(getArea(1, "123"));
借助 Babel 实现装饰器
上述的两个例子是函数式的装饰器,而我们还可以借助 Babel 来实现另外一种装饰器。接下来我们就来尝试一下另外一种装饰器的写法。
首先我们先安装好如下几个 babel 插件:
- @babel/cli
- @babel/core
- @babel/plugin-proposal-decorators
接下来配置 babel:
{
"presets": ["@babel/preset-env"],
"plugins": [["@babel/plugin-proposal-decorators", { "version": "2022-03" }]]
}
属性上添加装饰器
import { log } from "./log.decorator";
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
@log
getAge() {
return `${this.name} is ${this.age} years old.`;
}
}
let gus = new Person("Tom", 21);
const gusAge = gus.getAge();
console.log(gusAge);
export const log = (value, { kind, name }) => {
if (kind === "method") {
return function (...args) {
console.log(
`${name} deforator logged at: ${new Date().toLocaleDateString()}`
);
try {
const result = value.apply(this, args);
return result;
} catch (e) {
console.log(`Error: ${e}`);
throw e;
}
};
}
};
类上添加装饰器
@logged
class Person {}
export const logged = (val, { kind, name }) => {
if (kind === "class") {
return class extends val {
constructor(...args) {
super(...args);
console.log(
`Construction an instance of ${name} with arguments ${args.join(
", "
)}`
);
}
};
}
};