Decorator Pattern
装饰器模式的实现与应用场景
问题
什么是装饰器模式?如何在 JavaScript 中实现装饰器模式?
解答
装饰器模式允许在不修改原有对象的情况下,动态地给对象添加新的功能。
基础实现
// 基础组件
class Coffee {
cost() {
return 10;
}
description() {
return '咖啡';
}
}
// 装饰器基类
class CoffeeDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost();
}
description() {
return this.coffee.description();
}
}
// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 3;
}
description() {
return this.coffee.description() + ' + 牛奶';
}
}
// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1;
}
description() {
return this.coffee.description() + ' + 糖';
}
}
// 使用:可以任意组合
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.description()); // 咖啡 + 牛奶 + 糖
console.log(coffee.cost()); // 14
函数装饰器
// 通用函数装饰器
function withLogging(fn) {
return function (...args) {
console.log(`调用 ${fn.name},参数:`, args);
const result = fn.apply(this, args);
console.log(`返回值:`, result);
return result;
};
}
function withTiming(fn) {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${fn.name} 执行时间: ${end - start}ms`);
return result;
};
}
// 原始函数
function add(a, b) {
return a + b;
}
// 组合装饰
const decoratedAdd = withLogging(withTiming(add));
decoratedAdd(1, 2);
// 调用 add,参数: [1, 2]
// add 执行时间: 0.01ms
// 返回值: 3
ES7 装饰器语法
// 方法装饰器
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
function log(target, key, descriptor) {
const original = descriptor.value;
descriptor.value = function (...args) {
console.log(`调用 ${key},参数:`, args);
return original.apply(this, args);
};
return descriptor;
}
class Calculator {
@log
@readonly
add(a, b) {
return a + b;
}
}
// 类装饰器
function withTimestamp(Class) {
return class extends Class {
constructor(...args) {
super(...args);
this.createdAt = new Date();
}
};
}
@withTimestamp
class User {
constructor(name) {
this.name = name;
}
}
实际应用:防抖装饰器
function debounce(delay) {
return function (target, key, descriptor) {
const original = descriptor.value;
let timer = null;
descriptor.value = function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
original.apply(this, args);
}, delay);
};
return descriptor;
};
}
class SearchBox {
@debounce(300)
search(keyword) {
console.log('搜索:', keyword);
// 发起请求...
}
}
关键点
- 装饰器通过包装对象来扩展功能,不修改原对象
- 多个装饰器可以叠加使用,顺序影响执行结果
- 函数装饰器利用闭包和高阶函数实现
- ES7 装饰器本质是语法糖,编译后仍是函数调用
- 常见应用:日志、缓存、权限校验、防抖节流
目录