各位靓仔靓女,早上好! 很高兴今天能和大家聊聊JavaScript里一个挺有意思的特性:Decorators(装饰器)。 别害怕,听起来好像很高大上,但其实它就是个语法糖,让你的代码更优雅、更易读。 今天咱们就来深入浅出地扒一扒它,看看如何编写函数和类的装饰器。
开场白:为什么要用装饰器?
想象一下,你有一个函数,需要在执行前后做一些额外的事情,比如记录日志、验证权限、缓存结果等等。 你可能会这样做:
function myFunction() {
console.log("函数开始执行..."); // 记录日志
// ... 函数的核心逻辑 ...
console.log("函数执行完毕..."); // 记录日志
}
如果有很多函数都需要做类似的事情,那你就要在每个函数里都写一遍这些额外的逻辑。 这不仅重复劳动,而且让代码变得臃肿不堪,难以维护。
这时候,装饰器就派上用场了! 它可以让你把这些额外的逻辑抽离出来,像给函数“穿衣服”一样,动态地给函数添加功能,而不用修改函数本身的逻辑。
什么是装饰器?
简单来说,装饰器就是一个函数,它接收一个函数或类作为参数,并返回一个新的函数或类,这个新的函数或类通常会在原有的函数或类上添加一些额外的功能。
装饰器的语法
在JavaScript里,装饰器使用 @
符号来标记。 例如:
@log
function myFunction() {
// ...
}
这里的 @log
就是一个装饰器,它会作用于 myFunction
函数。
编写函数装饰器
函数装饰器接收一个函数作为参数,并返回一个新的函数。 新的函数通常会在调用原始函数之前或之后执行一些额外的逻辑。
我们来编写一个简单的日志记录装饰器:
function log(target, name, descriptor) {
const original = descriptor.value;
if (typeof original !== 'function') {
throw new TypeError('只能装饰函数');
}
descriptor.value = function (...args) {
console.log(`调用函数 ${name},参数:`, args);
const result = original.apply(this, args);
console.log(`函数 ${name} 返回值:`, result);
return result;
};
return descriptor;
}
class MyClass {
@log
add(a, b) {
return a + b;
}
}
const myInstance = new MyClass();
const sum = myInstance.add(1, 2);
console.log("最终结果:", sum);
上面的代码中,log
就是一个函数装饰器。 它接收三个参数:
target
: 被装饰的类本身(如果是类方法)或者undefined
(如果是独立的函数)。name
: 被装饰的方法或属性的名称。descriptor
: 属性描述符,包含value
(函数本身),writable
,enumerable
,configurable
等属性。
装饰器内部首先获取到原始函数 original
,然后创建一个新的函数,这个新的函数会在调用原始函数之前和之后记录日志。 最后,将新的函数赋值给 descriptor.value
,并返回 descriptor
。
当我们调用 myInstance.add(1, 2)
时,实际上调用的是被装饰器修改后的函数,它会先记录日志,然后调用原始的 add
函数,最后再记录返回值日志。
更通用一点的装饰器
上面的装饰器只适用于简单的日志记录。 如果我们需要更灵活的装饰器,可以接受参数,例如,指定日志的前缀:
function logWithPrefix(prefix) {
return function (target, name, descriptor) {
const original = descriptor.value;
if (typeof original !== 'function') {
throw new TypeError('只能装饰函数');
}
descriptor.value = function (...args) {
console.log(`${prefix} - 调用函数 ${name},参数:`, args);
const result = original.apply(this, args);
console.log(`${prefix} - 函数 ${name} 返回值:`, result);
return result;
};
return descriptor;
};
}
class MyClass {
@logWithPrefix("DEBUG")
add(a, b) {
return a + b;
}
}
const myInstance = new MyClass();
const sum = myInstance.add(1, 2);
console.log("最终结果:", sum);
logWithPrefix
函数返回一个装饰器函数,这个装饰器函数接收 target
, name
, descriptor
作为参数,并执行类似上面的日志记录操作,只不过在日志信息中添加了指定的前缀。
编写类装饰器
类装饰器接收一个类作为参数,并返回一个新的类。 新的类通常会在原有的类上添加一些新的属性或方法,或者修改原有的属性或方法。
我们来编写一个简单的类装饰器,给类添加一个 createdAt
属性,记录类的创建时间:
function createdAt(constructor) {
return class extends constructor {
constructor(...args) {
super(...args);
this.createdAt = new Date();
}
};
}
@createdAt
class MyClass {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const myInstance = new MyClass("Alice");
myInstance.sayHello();
console.log("创建时间:", myInstance.createdAt);
上面的代码中,createdAt
就是一个类装饰器。 它接收一个类 constructor
作为参数,并返回一个新的类,这个新的类继承自原始的类,并在构造函数中添加了 createdAt
属性。
类装饰器的一些高级用法
-
修改类的原型
类装饰器可以修改类的原型,添加新的方法或属性。 例如,我们可以给类添加一个
toString
方法:function addToString(constructor) { constructor.prototype.toString = function () { return `[object ${constructor.name}]`; }; } @addToString class MyClass { constructor(name) { this.name = name; } } const myInstance = new MyClass("Bob"); console.log(myInstance.toString());
-
替换整个类
类装饰器可以返回一个完全不同的类,替换掉原始的类。 这通常用于 mock 测试,或者根据不同的环境选择不同的实现。
function mockClass(constructor) { return class MockClass { constructor() { console.log("使用 MockClass!"); } mockMethod() { console.log("这是 Mock 方法!"); } }; } @mockClass class MyClass { constructor() { console.log("真正的 MyClass!"); } realMethod() { console.log("这是 Real 方法!"); } } const myInstance = new MyClass(); // myInstance.realMethod(); // 报错,因为 MyClass 被替换了 myInstance.mockMethod();
装饰器的应用场景
装饰器在实际开发中有很多应用场景,例如:
- 日志记录: 记录函数或方法的调用信息,方便调试和排错。
- 权限验证: 验证用户是否有权限访问某个函数或方法。
- 缓存: 缓存函数的计算结果,避免重复计算。
- 性能监控: 统计函数的执行时间,分析性能瓶颈。
- 依赖注入: 将依赖注入到类中,实现解耦。
- 数据验证: 验证数据的格式是否正确。
装饰器的一些注意事项
- 装饰器的执行顺序: 如果一个函数或类被多个装饰器装饰,装饰器的执行顺序是从下往上,从里往外。
- 装饰器的参数: 装饰器可以接收参数,也可以不接收参数。 接收参数的装饰器需要返回一个函数,这个函数才是真正的装饰器。
- 装饰器的兼容性: JavaScript 的装饰器语法目前还是一个提案,需要使用 Babel 等工具进行转译才能在浏览器中运行。 不过,TypeScript 原生支持装饰器语法。
一些常用的表格
装饰器类型 | 接收参数 | 返回值 | 作用 |
---|---|---|---|
类装饰器 | 类构造函数 | 新的类/构造函数 | 增强或替换整个类。可以修改类的原型、添加新的方法/属性,甚至完全替换这个类。 |
方法装饰器 | target, name, descriptor | descriptor | 增强类的某个方法。 可以修改方法的行为,例如添加日志、权限验证等。descriptor 包含了方法的各种属性,如 value (方法本身)、writable、enumerable、configurable。 修改 descriptor.value 来修改方法的实现。 |
属性装饰器 | target, name | descriptor | 增强类的某个属性。 通常用于对属性进行一些预处理或验证。 target 在类装饰器中是类的构造函数,在实例装饰器中是类的原型对象。 name 是属性名。 descriptor 包含了属性的配置信息,例如 get、set 方法。 |
参数装饰器 | target, name, index | void | 增强方法的某个参数。 target 是类构造函数或类的原型对象(取决于是否是静态方法)。 name 是方法名。 index 是参数的索引。 参数装饰器通常用于记录参数信息,或者进行一些参数验证。 由于无法直接修改参数本身,参数装饰器更多的是用于元数据管理,例如配合反射 API 来实现依赖注入。 |
总结
装饰器是JavaScript里一个强大的特性,它可以让你以一种声明式的方式给函数和类添加额外的功能,提高代码的可读性和可维护性。 虽然目前还需要使用 Babel 等工具进行转译,但相信随着 JavaScript 语言的不断发展,装饰器会越来越普及。
希望今天的讲解对大家有所帮助。 以后写代码的时候,不妨试试装饰器,让你的代码更上一层楼! 拜拜!