JavaScript中的闭包
七崽爱吃小饼干2026/01/20阅读 0专栏 JavaScript
一、什么是闭包?
先给一个通俗易懂的定义: 闭包 是指一个函数能够访问并记住其外层(父级)函数作用域中的变量,即使外层函数已经执行完毕并退出。简单来说,闭包让“内部函数”可以“记住”并访问“外部函数”的变量,突破了函数作用域的限制。
核心原理(用代码举例)
javascript
// 外层函数
function outerFunc() {
// 外层函数的局部变量(本应在outerFunc执行完后销毁)
let outerVar = "我是外层变量";
// 内部函数(闭包函数)
function innerFunc() {
// 内部函数访问外层函数的变量
console.log(outerVar);
}
// 返回内部函数,让它能在外部被调用
return innerFunc;
}
// 执行外层函数,得到内部函数的引用
const myClosure = outerFunc();
// 此时outerFunc已经执行完毕,但innerFunc依然能访问outerVar
myClosure(); // 输出:我是外层变量
关键逻辑解释:
outerFunc执行后,按常理其内部的outerVar应该被垃圾回收机制销毁;- 但因为
innerFunc引用了outerVar,且innerFunc被返回并赋值给myClosure(在全局作用域中存在),所以 JS 引擎会保留outerVar的内存,形成闭包; - 后续调用
myClosure()时,依然能访问到outerVar。
二、闭包的典型应用场景
闭包在实际开发中非常常用,以下是最典型的 4 个应用场景:
1. 保存私有变量(模块化/封装)
闭包可以创建“私有变量”——只能通过特定方法访问,无法直接修改,避免全局变量污染。
javascript
function createCounter() {
// 私有变量:外部无法直接访问
let count = 0;
// 返回包含多个方法的对象,这些方法形成闭包,能访问count
return {
increment: () => count++, // 增加计数
decrement: () => count--, // 减少计数
getCount: () => count // 获取当前计数
};
}
// 创建计数器实例
const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increment();
console.log(counter.getCount()); // 1
// 无法直接修改count(count是私有变量)
console.log(counter.count); // undefined
2. 柯里化(函数参数复用)
闭包可以实现函数柯里化,将多参数函数拆解为单参数函数,复用部分参数。
javascript
// 柯里化示例:计算a*b
function multiply(a) {
// 闭包记住a的值
return function(b) {
return a * b;
};
}
// 复用参数a=2
const double = multiply(2);
console.log(double(3)); // 6
console.log(double(5)); // 10
// 复用参数a=3
const triple = multiply(3);
console.log(triple(4)); // 12
3. 防抖/节流函数(性能优化)
防抖(debounce)和节流(throttle)是前端高频使用的性能优化手段,核心依赖闭包保存状态(如定时器ID、上次执行时间)。
javascript
// 防抖函数:频繁触发时,只执行最后一次
function debounce(fn, delay) {
let timerId; // 闭包保存定时器ID
return function(...args) {
// 每次触发时清除之前的定时器
clearTimeout(timerId);
// 重新设置定时器
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 示例:输入框输入后1秒执行查询
const input = document.querySelector('input');
input.addEventListener('input', debounce(function(e) {
console.log('查询:', e.target.value);
}, 1000));
4. 循环中保存变量(经典问题)
在 for 循环中绑定事件时,闭包可以解决“循环变量共享”导致的结果不符合预期的问题。
javascript
// 问题场景:循环绑定点击事件,不加闭包会导致所有按钮点击都输出5
for (var i = 0; i < 5; i++) {
// 用立即执行函数(IIFE)创建闭包,保存每次循环的i值
(function(index) {
const btn = document.createElement('button');
btn.innerText = `按钮${index}`;
btn.onclick = function() {
alert(`你点击了按钮${index}`);
};
document.body.appendChild(btn);
})(i);
}
总结
- 闭包的核心:内部函数访问外层函数的变量,且外层函数执行后变量不被销毁,本质是作用域链的特殊表现;
- 核心应用:封装私有变量、函数柯里化、防抖/节流、解决循环变量共享问题;
- 注意事项:闭包会保留外层变量的内存,滥用可能导致内存泄漏,需在不需要时手动释放(如将闭包引用赋值为 null)。