Appearance
👋 前言
在前端开发中,闭包(Closure) 是 JavaScript 的一个核心概念,也是面试高频考点。但很多同学在面对闭包时,要么一知半解,要么只会用套路题解题,实际上对其底层原理和应用场景并不清晰。
本文将带你从定义、原理、经典例子到应用场景,全面吃透闭包,掌握你必须知道的 JS 基础能力。
📘 一、闭包的定义(官方 + 通俗)
MDN 定义:闭包是指那些能够访问自由变量的函数。
自由变量:指在函数中使用的,但既不是函数参数,也不是函数内部声明的变量。
闭包 = 函数 + 函数能够访问的自由变量的集合(作用域)
✅ 通俗理解:
函数记住了它被定义时的作用域 —— 哪怕这个函数是在另一个作用域中被调用的。
🔍 二、闭包的经典案例
📌 案例 1:定时器和 var
的陷阱
js
for (var i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
// 输出:5 5 5 5 5
为什么不是 0,1,2,3,4?
因为
setTimeout
是异步的,循环早就结束了,此时i = 5
,闭包引用的是同一个 i 变量。
✅ 正确写法:使用 let
或立即执行函数(IIFE)
js
// 方式 1:let(块级作用域)
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 1000);
}
// 方式 2:IIFE + 闭包
for (var i = 0; i < 5; i++) {
((j) => {
setTimeout(() => console.log(j), 1000);
})(i);
}
📌 案例 2:函数工厂
js
function makeCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 2
函数
counter
是一个闭包,记住了makeCounter
的作用域链,即count
始终存在。
🧠 三、闭包的原理(作用域链)
闭包的本质是:函数在定义时就已经“捕获”了它的外部作用域。
作用域链图示:
js
function outer() {
let a = 10;
return function inner() {
console.log(a); // 访问的是 outer 的作用域
};
}
当 inner()
被返回并在外部执行时,它依然可以访问 a
,因为 JS 引擎在创建函数时,会创建作用域链,把它定义时的作用域环境一起存起来。
💡 四、闭包的应用场景
应用场景 | 示例 |
---|---|
私有变量 | 使用闭包模拟私有变量,避免全局污染 |
防抖 / 节流函数 | 利用闭包保存定时器变量 |
工厂函数 | 工厂模式中生成带状态的函数 |
缓存 | 封装函数,缓存某次计算结果 |
异步操作(回调) | 保持异步状态变量的正确性 |
⚠️ 五、闭包的注意事项
闭包会导致变量不会被垃圾回收
- 长期存在的闭包可能导致内存泄漏,尤其是绑定在全局对象上的函数。
不合理使用闭包会增加代码复杂度
- 建议只在需要记住状态或函数返回函数的场景中使用闭包。
✅ 总结
闭包是函数 + 定义时的作用域
它能访问外部函数的局部变量
理解作用域链,是掌握闭包的关键
广泛应用在工厂函数、私有变量、节流防抖、缓存中
注意内存管理,避免闭包导致的泄露
📢 最后一句话
闭包不是“黑魔法”,而是 JS 函数式编程的基石。掌握闭包,能帮你写出更高效、结构更清晰的代码。
如果你觉得有帮助,不妨点个赞 👍 或收藏 🌟