题目 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 是它的属性之一。