JavaScript 中的闭包是如何工作的?

Javascript教程 2025-11-05

JavaScript 中的闭包是如何工作的?

我刚开始学习 JavaScript 的时候,“闭包”这个词听起来很吓人。

后来我才意识到,闭包其实就是一些能记住一些东西的函数——没什么神奇的,只是实用而已。

本文用通俗易懂的语言解释了闭包的概念,并提供了简短的代码示例和一个便于面试回答的答案。我们力求简洁实用。

🧩 什么是闭幕式?

是指一个函数记住并使用其外部(父)函数中的变量——即使外部函数已经运行完毕。

你可以把它想象成一个背包🎒:当一个函数被创建时,它会把周围的变量都装进这个背包里。即使父函数返回,内部函数仍然可以访问背包里的这些变量。

💡 非常简单的例子

function outerFunction() {
  let count = 0;

  function innerFunction() {
    count++;
    console.log("Count is:", count);
  }

  return innerFunction;
}

const counter = outerFunction();

counter(); // Count is: 1
counter(); // Count is: 2
counter(); // Count is: 3

这说明了一种闭包效应:即使操作完成后,

innerFunction访问权限仍然保留。这种被记住的访问权限就是一种闭包效应。countouterFunction

🧠 逐步讲解:发生了什么?

  1. 调用outerFunction()→ 它会创建一个局部变量count = 0

  2. 定义innerFunction()内部outerFunction()

  3. innerFunction从……返回outerFunction

  4. 即使outerFunction结束了,innerFunction仍然记得count

  5. 每次调用都会更新闭包中存储的counter()相同内容。count

🔐 实际应用:隐私数据(计数器模块)

闭包可以帮助你隐藏数据(使其成为私有数据),并且只公开受控方法。

function createCounter() {
  let count = 0; // private

  return {
    increment() {
      count++;
      console.log("Count:", count);
    },
    decrement() {
      count--;
      console.log("Count:", count);
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // Count: 1
counter.increment(); // Count: 2
console.log(counter.getCount()); // 2
// direct access like counter.count → undefined

优点:count只能通过提供的方法进行更改。这种模式避免了全局变量和意外修改。

⏰ 异步代码的闭包

闭包可以完美地与定时器、承诺和回调函数配合使用。

function greetUser(name) {
  setTimeout(function () {
    console.log("Hello, " + name);
  }, 1000);
}

greetUser("Saurav"); // After 1s: Hello, Saurav

即使返回结果,由于闭包的存在greetUser,超时回调函数仍然会记住超时状态。name

⚙️ 函数工厂(另一种有用的模式)

闭环可以实现定制化功能。

function multiplyBy(factor) {
  return function (number) {
    return number * factor;
  };
}

const double = multiplyBy(2);
const triple = multiplyBy(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

每个返回的函数都会记住自己的标识符factor。这既简单又强大。

⚠️ 初学者常犯的错误

  • 共享循环变量问题:在循环中使用var闭包可能会造成混淆。建议使用let或创建新的作用域。

  • 内存问题:闭包会保留对外部变量的引用;避免不必要地保存大量数据。

  • 作用域混淆:内部作用域和外部作用域中相同的变量名会相互掩盖——务必明确变量的定义位置。

🚀 在实际代码中哪里可以看到闭包

  • React hooks(useStateuseEffect)底层使用了闭包。

  • 访问外部变量的事件处理程序。

  • 用于封装的模块和工厂模式。

  • 柯里化和函数式实用程序。

🧠 要点总结

闭包本质上是一个内部函数,它可以记住外部作用域中的变量。

使用闭包可以保护数据隐私记住状态编写模块化代码

🎯 额外内容:如何在面试中解释闭包(附示例)

当面试官问“什么是闭包?”“解释一下 JavaScript 中的闭包”时,你的回答要简短,然后举一个简单的例子。

简答题(30-45秒)

闭包是指即使外部函数执行完毕,内部函数仍然会记住其外部作用域中的变量。换句话说,内部函数会携带其词法环境。闭包使得 JavaScript 中的数据隐私和有状态函数成为可能。

后续:一个可以在白板或代码中演示的简单示例

function makeGreeter(name) {
  return function greet() {
    console.log("Hello, " + name);
  };
}

const greetSaurav = makeGreeter("Saurav");
greetSaurav(); // Hello, Saurav

边演示边解释:

  • makeGreeter创建一个本地name

  • 它返回greet,其中使用了name

  • 即使makeGreeter退货后,greet仍然记得name——这就算是一种了结。

常见的面试后续问题及回答方法

  • 问:闭包有什么用?答:它适用于有状态函数和无需全局变量的数据隐私保护。它有助于实现模块、工厂和回调函数。

  • 问:有什么需要注意的地方吗?答:如果不小心,闭包可能会无意中让大型对象保持存活状态,导致内存占用过高。此外,循环变量捕获错误也可能导致结果混乱——使用闭包let可以避免这种情况。

  • 问:你能展示一下闭包导致的 bug 吗?答:可以——旧代码var中使用带有异步回调的循环会捕获同一个变量。请展示使用立即执行let函数表达式 (IIFE) 修复后的版本。

迷你白板挑战(简单的编程任务)

任务:编写一个函数makeCounter(),返回一个包含 `count`incrementget`count` 方法的对象。`count`get方法返回当前计数。

快速写作的解决方案:

function makeCounter() {
  let count = 0;
  return {
    increment() { count++; },
    get() { return count; }
  };
}

const c = makeCounter();
c.increment();
console.log(c.get()); // 1

为什么这是一个好的面试回答:

  • 简短、正确,并且表明你了解数据关闭和数据隐私。

  • 如果需要,您可以对其进行扩展(添加decrement、验证)。