在javascript开发中,我们经常需要在不修改原有函数核心逻辑的情况下,为其添加额外的功能,比如执行前后打印日志、缓存函数返回结果、校验调用权限等。装饰器模式恰好满足这个需求,而javascript的闭包特性可以完美支撑装饰器模式的实现,让我们可以动态地为函数扩展能力。

闭包与装饰器模式基础
闭包的核心特性
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的常见方式就是在一个函数内部创建另一个函数。闭包可以记住并访问其词法作用域,即使这个作用域已经执行完毕。简单来说,闭包让函数可以携带其创建时的环境信息,这也是它能实现装饰器的基础。
装饰器模式的定义
装饰器模式属于结构型设计模式,它的核心思想是不改变原有对象的自身结构,通过附加的方式为其添加额外的职责。在javascript中,我们通常用装饰器来增强函数的功能,装饰后的函数会保留原有函数的核心逻辑,同时新增我们定义的功能。
用闭包实现装饰器模式的核心思路
实现的核心逻辑可以分为三步:
- 定义一个装饰器函数,这个函数接收需要被增强的原始函数作为参数
- 在装饰器函数内部返回一个新的函数,这个新函数就是增强后的函数
- 新函数内部先执行我们新增的额外逻辑,再调用原始函数,也可以根据需求调整调用顺序,或者修改原始函数的参数、返回值
因为闭包的特性,返回的新函数可以访问到装饰器函数作用域中的原始函数,所以即使装饰器函数执行完毕,新函数依然可以正常调用原始函数,这就是闭包支撑装饰器实现的关键。
常见场景实现示例
为函数添加执行日志
很多时候我们需要在函数执行前后打印日志,记录函数的调用时间和参数,用闭包装饰器可以很方便地实现:
// 日志装饰器函数
function withLog(fn) {
// 返回的新函数就是增强后的函数,闭包可以访问fn
return function(...args) {
console.log(`函数开始执行,参数:${JSON.stringify(args)},时间:${new Date().toLocaleString()}`);
// 调用原始函数,保留原有逻辑
const result = fn.apply(this, args);
console.log(`函数执行结束,返回值:${JSON.stringify(result)},时间:${new Date().toLocaleString()}`);
return result;
};
}
// 原始的业务函数
function add(a, b) {
return a + b;
}
// 用装饰器增强原始函数
const addWithLog = withLog(add);
// 调用增强后的函数
addWithLog(1, 2);
上面的代码中,withLog是装饰器函数,它接收原始函数add,返回的新函数通过闭包访问到了fn(也就是原始函数),在执行前后添加了日志逻辑,没有修改add函数本身的代码。
实现函数结果缓存
对于计算成本较高的函数,我们可以用装饰器为其添加缓存能力,相同参数的调用直接返回缓存结果,避免重复计算:
// 缓存装饰器函数
function withCache(fn) {
// 用对象存储缓存结果,闭包让这个缓存对象可以一直被访问
const cache = {};
return function(...args) {
// 把参数作为缓存的键
const key = JSON.stringify(args);
// 如果缓存中有结果,直接返回
if (cache.hasOwnProperty(key)) {
console.log('从缓存中获取结果');
return cache[key];
}
// 没有缓存则调用原始函数计算
const result = fn.apply(this, args);
// 把结果存入缓存
cache[key] = result;
return result;
};
}
// 原始的计算函数,模拟耗时计算
function heavyCalculate(num) {
console.log('执行耗时计算');
// 模拟计算耗时
let total = 0;
for (let i = 0; i < 100000000; i++) {
total += i;
}
return total + num;
}
// 增强后的带缓存的计算函数
const cachedCalculate = withCache(heavyCalculate);
// 第一次调用,会执行计算
cachedCalculate(10);
// 第二次调用相同参数,直接从缓存返回
cachedCalculate(10);
添加权限校验逻辑
如果某些函数需要校验调用权限,比如只有登录用户才能调用,也可以用装饰器实现:
// 模拟用户登录状态
let isLogin = false;
// 权限校验装饰器
function withAuth(fn) {
return function(...args) {
if (!isLogin) {
console.log('请先登录再操作');
return;
}
// 已登录则执行原始函数
return fn.apply(this, args);
};
}
// 原始的需要权限的操作函数
function deleteItem(id) {
console.log(`删除id为${id}的项目`);
return true;
}
// 增强后的带权限校验的函数
const authDelete = withAuth(deleteItem);
// 未登录时调用
authDelete(1);
// 修改登录状态后调用
isLogin = true;
authDelete(1);
装饰器的通用化封装
如果有多个不同的装饰逻辑,我们可以把装饰器的创建过程再抽象一层,让使用更方便:
// 通用装饰器创建函数,接收装饰逻辑
function createDecorator(decorateLogic) {
return function(fn) {
return function(...args) {
// 执行装饰逻辑,装饰逻辑可以决定什么时候调用原始函数,或者修改参数返回值
return decorateLogic.call(this, fn, args);
};
};
}
// 用通用装饰器创建日志装饰器
const withLogDecorator = createDecorator(function(fn, args) {
console.log('函数开始执行');
const result = fn.apply(this, args);
console.log('函数执行结束');
return result;
});
// 用通用装饰器创建缓存装饰器
const withCacheDecorator = createDecorator(function(fn, args) {
const key = JSON.stringify(args);
if (!this.cache) {
this.cache = {};
}
if (this.cache.hasOwnProperty(key)) {
return this.cache[key];
}
const result = fn.apply(this, args);
this.cache[key] = result;
return result;
});
// 原始函数
function multiply(a, b) {
return a * b;
}
// 组合多个装饰器,先加缓存再加日志
const enhancedMultiply = withLogDecorator(withCacheDecorator(multiply));
enhancedMultiply(3, 4);
enhancedMultiply(3, 4);
注意事项
- 装饰器返回的新函数的
this指向需要注意,最好用apply或者call来调用原始函数,保证this和原始调用场景一致 - 如果原始函数有函数名、长度等属性,装饰后的函数会丢失这些属性,需要的话可以手动把这些属性复制到新函数上
- 不要过度使用装饰器,简单的功能直接修改函数可能更易读,装饰器适合需要复用的增强逻辑场景
javascript闭包装饰器模式函数增强修改时间:2026-06-15 10:36:21