Skip to content

闭包

✍️天畅🕐2025-05-19

👋 前言

在前端开发中,闭包(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 引擎在创建函数时,会创建作用域链,把它定义时的作用域环境一起存起来。

💡 四、闭包的应用场景

应用场景示例
私有变量使用闭包模拟私有变量,避免全局污染
防抖 / 节流函数利用闭包保存定时器变量
工厂函数工厂模式中生成带状态的函数
缓存封装函数,缓存某次计算结果
异步操作(回调)保持异步状态变量的正确性

⚠️ 五、闭包的注意事项

  1. 闭包会导致变量不会被垃圾回收

    • 长期存在的闭包可能导致内存泄漏,尤其是绑定在全局对象上的函数。
  2. 不合理使用闭包会增加代码复杂度

    • 建议只在需要记住状态或函数返回函数的场景中使用闭包。

✅ 总结

  • 闭包是函数 + 定义时的作用域

  • 它能访问外部函数的局部变量

  • 理解作用域链,是掌握闭包的关键

  • 广泛应用在工厂函数、私有变量、节流防抖、缓存中

  • 注意内存管理,避免闭包导致的泄露

📢 最后一句话

闭包不是“黑魔法”,而是 JS 函数式编程的基石。掌握闭包,能帮你写出更高效、结构更清晰的代码。

如果你觉得有帮助,不妨点个赞 👍 或收藏 🌟