题目 24 / 31

闭包捕获的变量和 this 在这里是如何各自独立演化的?

知识点: 闭包难度: 高级

IIFE 的参数 num 被闭包捕获后独立演化,而每一次 this 都是在各自调用点重新解析的——包括全局的 num 绑定本身。

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

预测输出结果

预测三行输出。请把闭包捕获的 num 和 this.num 分开跟踪,并注意全局的 var 同时也是全局对象的属性。

var num = 10;
var obj = { num: 20 };

obj.fn = (function (num) {
  this.num = num * 3;
  num++;
  return function (n) {
    this.num += n;
    num++;
    console.log(num);
  };
})(obj.num);

var fn = obj.fn;
fn(5);
obj.fn(10);
console.log(num, obj.num);
查看答案与完整解析

正确输出

22
23
65 30

为什么会这样

这个 IIFE 是以无接收者的方式立即调用的,因此它的 this 是全局对象;this.num = num * 3 把全局的 num 属性(也就是外层的 var num)设置为 60。闭包自身的参数 num 从 20 开始,在函数返回、被存入 obj.fn 之前,被 num++ 递增为 21。

fn(5) 是通过裸变量 fn 调用返回的那个函数的,因此默认绑定生效:this 是全局对象。this.num += 5 让全局的 num 变成 65,闭包的 num 变成 22,console.log(num) 读取的是闭包自己的 num,打印 22。

obj.fn(10) 则通过隐式绑定调用同一个函数,this 是 obj:this.num += 10 让 obj.num 变成 30,闭包的 num 变成 23,这一行打印 23。最后的 console.log(num, obj.num) 读取的是外层的 num——它和 this.num 之前修改的全局对象属性其实是同一个绑定,此时已是 65——以及此时为 30 的 obj.num,于是打印 "65 30"。

这道题考察什么

  • 闭包变量只被捕获一次,之后独立于 this 演化
  • this 在每次调用时都会重新决定:fn(5) 是默认绑定,obj.fn(10) 是隐式绑定
  • 顶层的 var 同时也是全局对象的属性,因此 this.num 和外层的 num 可能就是同一个绑定

运行环境前提

假设运行在经典(非模块)的非严格模式脚本中,无接收者调用的 this 是全局对象,顶层 var 是它的属性之一。

规范参考

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

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