闭包的定义:
闭包是指有权访问另一个函数作用域中变量的函数
闭包的形成:
当内部函数被返回并在外部作用域中调用时,闭包正式形成。此时外部函数已执行完毕,但内部函数仍能访问其变量
闭包demo:
function outer() {
let count = 0; // 外部函数的变量
function inner() {
count++; // 内部函数(闭包)访问外部变量
console.log(count);
}
return inner; // 返回闭包
}
const closure = outer(); // outer执行完毕,但count被闭包“记住”
closure(); // 输出 1
closure(); // 输出 2
闭包使用场景:
1. 数据封装与私有变量
闭包可以模拟面向对象编程中的私有变量,避免全局污染,实现数据封装
示例:计数器模块
const counterModule = (function() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getCount: function() {
return count;
}
};
})();
console.log(counterModule.increment()); // 1
console.log(counterModule.increment()); // 2
console.log(counterModule.decrement()); // 1
console.log(counterModule.getCount()); // 1
关键点:
- 通过 IIFE(立即执行函数)创建一个闭包,
count变量被内部函数引用,外部无法直接修改。 - 对外暴露的
increment、decrement和getCount方法可以操作count,但无法直接访问它。
2. 函数工厂(动态生成函数)
闭包可以根据输入参数动态生成具有特定行为的函数。
示例:生成乘法函数
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier; // 闭包记住了 multiplier 的值
};
}
const double = createMultiplier(2); // 生成一个“乘以2”的函数
const triple = createMultiplier(3); // 生成一个“乘以3”的函数
console.log(double(5)); // 10
console.log(triple(5)); // 15
关键点:
createMultiplier返回的函数是一个闭包,它记住了传入的multiplier参数。- 每次调用
createMultiplier会生成一个新的闭包,拥有独立的multiplier值。
3. 事件处理与回调
-
场景:在异步回调中访问外部变量。
-
示例
function setupButtons() { const buttons = document.querySelectorAll('button'); buttons.forEach((button, i) => { button.addEventListener('click', () => { console.log(`Button ${i} clicked`); // 闭包记住i的值 }); }); } -
作用:避免循环中因变量共享导致的错误(如
i始终为最终值)。
4. 防抖(Debounce)与节流(Throttle)
-
场景:控制高频事件的触发频率。
-
示例(防抖)
function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); // 闭包保留timer }; } const handleResize = debounce(() => console.log('Resized'), 200); window.addEventListener('resize', handleResize); -
作用:闭包保留定时器变量,确保延迟期间重复调用被忽略。
5. 模块模式(Module Pattern)
-
场景:封装模块,暴露有限接口。
-
示例
const module = (() => { let privateVar = 'secret'; function privateMethod() { return privateVar; } return { publicMethod: () => privateMethod() // 通过闭包访问私有成员 }; })(); console.log(module.publicMethod()); // 'secret' -
作用:利用闭包实现模块化,隐藏内部实现细节。
6. 记忆化(Memoization)缓存
-
场景:缓存函数计算结果,提升性能。
-
示例
function memoize(fn) { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); // 闭包保留cache return result; }; } -
作用:闭包保存缓存对象,避免重复计算。
7. 柯里化(Currying)
-
场景:将多参数函数转换为单参数函数链。
-
示例
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn(...args); } else { return function(...moreArgs) { return curried(...args, ...moreArgs); }; } }; } function sum(a, b, c) { return a + b + c; } const curriedSum = curry(sum); console.log(curriedSum(1)(2)(3)); // 6 -
作用:闭包逐步接收参数,实现灵活的函数调用。
闭包缺点及解决方式:
1. 内存消耗
-
变量无法释放:闭包会长期持有外部变量的引用,即使外部函数已执行完毕,这些变量仍会保留在内存中,可能导致内存泄漏。
function heavyFunction() { const hugeData = new Array(1000000).fill("data"); return () => console.log(hugeData[0]); // 闭包保留hugeData } const closure = heavyFunction(); // 即使不再需要closure,hugeData仍占用内存 -
解决方案:手动解除引用(如
closure = null)。
2. 性能开销
- 作用域链查找:闭包访问外部变量时需要沿着作用域链逐级查找,比访问局部变量稍慢(但现代引擎优化后影响较小)。
- 创建开销:频繁创建闭包可能增加内存分配和垃圾回收的负担
3. 共享变量导致的意外行为
- 闭包共享外部变量:多个闭包实例可能共享同一外部变量,导致状态被意外修改。
4. 调试复杂性
- 作用域链隐蔽性:闭包的作用域链可能使调试时难以追踪变量的来源,尤其是嵌套较深时。
5. 过度使用导致代码难以维护
- 逻辑分散:过度依赖闭包可能导致代码逻辑分散在多个作用域中,降低可读性。