题目 11 / 14

闭包如何保存不断变化的状态?

知识点: 闭包

fn 返回后,返回的函数仍然可以访问 fn 的 n 绑定。

使用执行可视化器解题在下方逐步观察每个绑定、函数调用和控制台输出。

预测输出结果

为什么反复调用 x() 会从 21 继续递增,而不是每次重新从 20 开始?

var n = 10
function fn() {
  var n = 20
  function f() {
    n++
    console.log(n)
  }
  f()
  return f
}

var x = fn()
x()
x()
console.log(n)
查看答案与完整解析

正确输出

21
22
23
10

为什么会这样

调用 fn 会创建值为 20 的局部 n。第一次调用 f 把它递增为 21。

fn 返回函数 f。因为 f 仍然引用外层 n 绑定,所以 fn 返回后该环境依然可达。

接下来两次 x() 修改的是同一个保留下来的绑定,得到 22 和 23;独立的全局 n 仍然是 10。

这道题考察什么

  • 闭包捕获
  • 保留的词法环境
  • 独立全局绑定

运行环境前提

可视化器把被保留的环境表示为规范中的机制,它与普通 JavaScript 对象属于不同类别。

规范参考

JavaScript 代码编辑器支持直接编辑或粘贴自定义代码
第 0 步 / 共 0 步
单步Space播放
按 → 单步 或 Space 自动播放
阶段 1: 编译预解析(变量提升与内存分配)
阶段 2: 代码执行(逐行赋值与调用求值)
单步执行原理解析

按键盘右方向键 “→” 进行单步调试,或按空格键 “Space” / 点击 “▶ 自动播放” 进行连续执行。

作用域链 (Scope Chain)
词法作用域链: 当前 AO → 外部 [[Scope]] → 全局 (GO)
暂无激活的作用域链
调用栈 (CALL STACK)执行上下文栈 (后进先出 LIFO)
后进先出 (LIFO) 栈帧
全局执行上下文 (GEC)
▶ 活动栈帧
堆内存空间 (HEAP & MEMORY SPACE)已分配对象 (GO, AO, FunctionObjects, ER)
ECMAScript 堆内存区
内存中暂无分配对象
控制台输出 (Console)
console.log(...) Stream
暂无输出产生