什么是装饰器模式?
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
在前端开发中,装饰器模式特别有用,因为我们经常需要在不修改原有组件或函数的情况下增强它们的功能。比如:
- 给组件添加日志记录功能
- 为函数添加性能监控
- 实现高阶组件(HOC)
- 添加权限控制
- 实现缓存功能
装饰器模式的实现方式
1. 函数装饰器实现
// 原始函数
function fetchData(url) {
console.log(`Fetching data from ${url}`);
return new Promise(resolve => {
setTimeout(() => resolve(`Data from ${url}`), 1000);
});
}
// 装饰器函数 - 添加缓存功能
function withCache(fn) {
const cache = new Map();
return async function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Returning cached result');
return cache.get(key);
}
const result = await fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用装饰器
const cachedFetch = withCache(fetchData);
// 第一次调用 - 从网络获取
cachedFetch('api/users').then(console.log);
// 输出: Fetching data from api/users
// 1秒后输出: Data from api/users
// 第二次调用同样的URL - 从缓存获取
cachedFetch('api/users').then(console.log);
// 输出: Returning cached result
// 立即输出: Data from api/users
2. 类装饰器实现
// 原始类
class UserService {
constructor() {
this.users = [];
}
addUser(user) {
this.users.push(user);
console.log(`User ${user.name} added`);
}
getUsers() {
return this.users;
}
}
// 装饰器 - 添加日志功能
function withLogging(ServiceClass) {
return class extends ServiceClass {
constructor(...args) {
super(...args);
console.log(`Service ${ServiceClass.name} initialized`);
}
addUser(user) {
console.log(`Before adding user: ${JSON.stringify(user)}`);
const result = super.addUser(user);
console.log(`After adding user: ${JSON.stringify(user)}`);
return result;
}
};
}
// 使用装饰器
const LoggedUserService = withLogging(UserService);
const service = new LoggedUserService();
service.addUser({ id: 1, name: 'John' });
// 输出:
// Service UserService initialized
// Before adding user: {"id":1,"name":"John"}
// User John added
// After adding user: {"id":1,"name":"John"}
3. ES7装饰器语法
ES7引入了装饰器语法糖,使装饰器模式更加简洁:
// 类装饰器
function logClass(target) {
console.log(`Class ${target.name} is defined`);
// 可以返回一个新的类来替换原类
return class extends target {
constructor(...args) {
super(...args);
console.log(`Instance of ${target.name} created`);
}
};
}
// 方法装饰器
function logMethod(target, name, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling method ${name} with args:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${name} returned:`, result);
return result;
};
return descriptor;
}
// 属性装饰器
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
// 使用装饰器
@logClass
class Calculator {
@readonly
version = '1.0';
@logMethod
add(a, b) {
return a + b;
}
}
const calc = new Calculator();
// 输出: Class Calculator is defined
// 输出: Instance of Calculator created
console.log(calc.add(2, 3));
// 输出: Calling method add with args: [2, 3]
// 输出: Method add returned: 5
// 输出: 5
calc.version = '2.0'; // 会抛出错误,因为属性是只读的
日常开发中的合理使用建议
- 高阶组件(HOC)模式 - React中常用的装饰器模式实现
// 高阶组件 - 添加用户认证功能
function withAuth(WrappedComponent) {
return class extends React.Component {
componentDidMount() {
if (!this.props.isAuthenticated) {
this.props.history.push('/login');
}
}
render() {
return this.props.isAuthenticated
? <WrappedComponent {...this.props} />
: null;
}
};
}
// 使用高阶组件
class ProfilePage extends React.Component {
render() {
return <div>User Profile</div>;
}
}
const ProtectedProfile = withAuth(ProfilePage);
// 在路由中使用
<Route path="/profile" component={ProtectedProfile} />
- 函数增强 - 为函数添加额外功能
// 性能监控装饰器
function measurePerformance(fn) {
return function(...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`Function ${fn.name} executed in ${end - start}ms`);
return result;
};
}
// 使用
const heavyCalculation = measurePerformance(function heavyCalculation() {
let sum = 0;
for (let i = 0; i < 100000000; i++) {
sum += i;
}
return sum;
});
heavyCalculation(); // 输出: Function heavyCalculation executed in 123.45ms
- API请求装饰器 - 统一处理错误和加载状态
// API请求装饰器
function withApiHandling(fn) {
return async function(...args) {
try {
this.setState({ loading: true, error: null });
const result = await fn.apply(this, args);
return result;
} catch (error) {
console.error('API Error:', error);
this.setState({ error: error.message });
throw error;
} finally {
this.setState({ loading: false });
}
};
}
// 在React组件中使用
class UserList extends React.Component {
state = { users: [], loading: false, error: null };
@withApiHandling
async fetchUsers() {
const response = await fetch('/api/users');
const users = await response.json();
this.setState({ users });
return users;
}
componentDidMount() {
this.fetchUsers();
}
render() {
const { users, loading, error } = this.state;
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
}
实际开发中的注意事项
- 装饰器执行顺序:装饰器是从上到下、从外到内执行的
function decorator1(value) {
console.log('Decorator 1');
return function(target) {
console.log('Decorator 1 applied');
};
}
function decorator2(value) {
console.log('Decorator 2');
return function(target) {
console.log('Decorator 2 applied');
};
}
@decorator1('value1')
@decorator2('value2')
class MyClass {}
// 输出顺序:
// Decorator 1
// Decorator 2
// Decorator 2 applied
// Decorator 1 applied
-
避免过度装饰:装饰器会增加代码的抽象层级,过度使用会使代码难以理解
-
保持装饰器职责单一:每个装饰器应该只做一件事
// 不好的做法 - 一个装饰器做多件事
function badDecorator(target) {
// 添加日志
// 添加缓存
// 添加权限检查
// ...
}
// 好的做法 - 拆分职责
@withLogging
@withCaching
@withAuth
class MyService {}
- 注意this绑定:在装饰器内部要正确处理this
function bindMethods(...methodNames) {
return function(target) {
methodNames.forEach(methodName => {
const originalMethod = target.prototype[methodName];
target.prototype[methodName] = function(...args) {
// 确保方法绑定到正确的this
return originalMethod.apply(this, args);
};
});
};
}
@bindMethods('handleClick', 'handleSubmit')
class MyComponent {
handleClick() {
console.log('Clicked', this); // this会正确指向组件实例
}
handleSubmit() {
console.log('Submitted', this);
}
}
- 装饰器与继承的区别:装饰器是组合而非继承
// 继承方式
class LoggedError extends Error {
constructor(message) {
super(message);
console.log('Error created:', message);
}
}
// 装饰器方式
function loggedError(ErrorClass) {
return class extends ErrorClass {
constructor(message) {
super(message);
console.log('Error created:', message);
}
};
}
@loggedError
class MyError extends Error {}
// 装饰器方式更灵活,可以应用到任何Error类
装饰器模式是前端开发中非常实用的设计模式,它能够帮助我们:
- 在不修改原有代码的情况下扩展功能
- 遵循开放-封闭原则(对扩展开放,对修改封闭)
- 实现关注点分离,保持代码整洁
- 通过组合而非继承来扩展功能
在实际项目中,合理使用装饰器模式可以显著提高代码的可维护性和可扩展性。
但也要注意不要过度使用,保持装饰器的单一职责,并注意装饰器的执行顺序和this绑定等问题。