JavaScript 闭包:通俗易懂的解释与底层原理

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

JavaScript 闭包是如何工作的:基于 Stack Overflow 示例的解析

\n\n

如果你曾经搜索过“JavaScript closures”或在 Stack Overflow 上阅读过讨论,那么你很可能见过那个著名的问题:“How do JavaScript closures work?”。这个帖子获得了数千次浏览和数百个回答——从简单的解释到对 V8 引擎的深入探讨。为什么?因为闭包是那种初看像魔法,但随后会成为你超级武器的概念之一。

\n\n

在这篇文章中,我们不仅仅是复述理论。我们将深入 JavaScript 的底层,分析内存中发生的情况,并最终理解为什么这段代码会以这种方式运行,而不是其他方式。准备好了吗?我们开始吧。

\n\n

什么是闭包?简洁的定义

\n\n

让我们从核心开始。闭包 (closure) 是一个函数,它“记住”了其外部作用域中的变量,即使这个外部函数已经执行完毕。

\n\n

听起来像咒语?我们直接看代码。这是你在 Stack Overflow 上能找到的经典示例:

\n\n
function createCounter() {\n    let count = 0;\n    return function() {\n        count++;\n        return count;\n    };\n}\n\nconst counter = createCounter();\nconsole.log(counter()); // 1\nconsole.log(counter()); // 2\nconsole.log(counter()); // 3
\n\n

这里,createCounter 返回一个匿名函数。这个内部函数“封闭”了变量 count。即使在 createCounter 执行完毕之后(我们以为它内部的所有变量都应该消失),count 仍然存在,并且在每次调用 counter() 时发生变化。这就是闭包。

\n\n

底层原理:词法环境

\n\n

要理解这是如何工作的,我们需要深入 JavaScript 引擎的内部。每个函数在创建时都有一个隐藏属性 [[Environment]]。它存储了对创建该函数的词法环境 (Lexical Environment) 的引用。

\n\n

词法环境就是一个对象,它包含:

\n
    \n
  • 环境记录 (Environment Record) —— 在该作用域中声明的所有变量和函数。
  • \n
  • 对外部环境的引用 (outer) —— 父级作用域。
  • \n
\n\n

当我们调用一个函数时,会为该调用创建一个新的词法环境。内部函数通过作用域链 (scope chain) 访问外部函数的变量。

\n\n

回到我们的示例。当我们声明 createCounter 时,会创建一个全局词法环境。其中包含变量 createCounter(函数)和 counter(目前为 undefined)。

\n\n

当我们调用 createCounter() 时:

\n
    \n
  1. 为该调用创建一个新的词法环境。其中包含变量 count = 0。
  2. \n
  3. 创建一个匿名函数。它的 [[Environment]] 指向当前环境(其中存在 count)。
  4. \n
  5. 该函数被返回并赋值给 counter。
  6. \n
\n\n

最有趣的部分来了:createCounter 执行完毕,但它的词法环境并没有被垃圾回收器删除!因为有一个指向它的引用——内部函数的 [[Environment]]。只要 counter 函数本身存在,这个环境就会一直留在内存中。

\n\n

现在,每次我们调用 counter() 时,都会为该调用创建一个新的环境,它通过作用域链看到那个包含 count 的旧环境。这样我们就得到了“记忆”的效果。

\n\n

为什么这很重要?实际应用场景

\n\n

闭包不仅仅是一个学术概念。它们被广泛使用。让我们分析几个实际例子。

\n\n

1. 私有变量(封装)

\n\n

在 JavaScript 中没有真正的

博客

书籍推荐