JavaScript 闭包(Closures)详解
1. 闭包是什么?
闭包(Closure) 是指一个函数能够访问并记住其词法作用域(Lexical Scope),即使该函数在其定义的作用域之外执行。简单来说:
-
函数 + 它所在的词法环境 = 闭包。
-
闭包允许函数访问它被创建时的作用域链,即使该函数在其他地方被调用。
2. 闭包的核心特点
-
函数嵌套函数(内部函数引用外部函数的变量)。
-
外部函数执行完毕后,内部函数仍能访问其变量(变量不会被垃圾回收)。
3. 闭包示例
示例 1:基本闭包
function outer() {
let count = 0; // 外部函数的变量
function inner() {
count++; // 内部函数访问外部变量
console.log(count);
}
return inner; // 返回内部函数
}
const counter = outer(); // outer() 执行完毕,但 count 仍被 inner 记住
counter(); // 输出 1
counter(); // 输出 2(count 保持状态)
解释:
-
outer()
执行后,count
变量本应被销毁,但由于inner()
引用了它,闭包保留了count
。 -
counter()
每次调用都会修改并记住count
的值。
示例 2:闭包实现私有变量
function createCounter() {
let privateCount = 0; // 私有变量,外部无法直接访问
return {
increment: function() {
privateCount++;
},
getCount: function() {
return privateCount;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
console.log(counter.privateCount); // undefined(无法直接访问)
解释:
-
privateCount
只能通过闭包提供的increment()
和getCount()
方法访问,实现了数据封装。
示例 3:循环中的闭包(经典面试题)
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3, 3, 3(不是预期的 0, 1, 2)
}, 1000);
}
问题:由于 var
没有块级作用域,setTimeout
回调执行时 i
已经是 3。
解决方案(用闭包或 let
):
// 方法 1:使用 IIFE(立即执行函数)创建闭包
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出 0, 1, 2
}, 1000);
})(i);
}
// 方法 2:使用 let(块级作用域)
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 0, 1, 2
}, 1000);
}
4. 闭包的优缺点
优点
-
数据私有化:隐藏变量,避免全局污染(如示例 2)。
-
保持状态:函数执行后,变量仍可被访问(如计数器示例)。
-
模块化开发:早期 JS 用闭包模拟模块(现代用 ES6
import/export
)。
缺点
-
内存泄漏:闭包会长期占用内存(变量未被释放),滥用可能导致性能问题。
-
调试困难:闭包的作用域链较复杂,可能增加代码理解难度。
5. 闭包的应用场景
-
防抖(Debounce)和节流(Throttle)
function debounce(fn, delay) { let timer; return function() { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, arguments), delay); }; }
-
柯里化(Currying)
function add(a) { return function(b) { return a + b; }; } const add5 = add(5); console.log(add5(3)); // 8
-
React Hooks(如
useState
)
React 的 Hooks 机制依赖闭包来记住状态。
6. 如何判断闭包?
-
如果一个函数访问了它外部的变量,并且该函数在外部作用域之外被调用,那么这就是闭包。
总结
-
闭包的本质:函数 + 其词法作用域。
-
核心作用:让函数“记住”它定义时的环境(变量)。
-
使用场景:私有变量、模块化、高阶函数(如防抖)、React Hooks 等。
-
注意事项:避免滥用,防止内存泄漏。
闭包是 JavaScript 的核心概念,理解它才能写出更高级的代码! 🚀