闭包在内部函数被返回且引用外部变量、并在外部调用时才真正形成;此时外部执行上下文不销毁,变量被持续持有。

理解闭包形成的关键时机
闭包不是函数定义时就“自动产生”的,而是当一个函数被返回(或传递出去)且**内部函数实际引用了外部函数的变量**,同时该内部函数在外部作用域中被调用时,才真正激活闭包机制。此时外部函数的执行上下文不会被销毁,其变量对象被内部函数持续持有。
看懂嵌套调用中的参数传递链
以这道高频题为例:
function fun(n, o) {
console.log(o);
return {
fun: function(m) {
return fun(m, n);
}
};
}
关键点在于:每次 return 的对象里 fun 方法,都闭包捕获了当前外层 fun 调用时的 n 值。而每次调用该 fun(m) 时,又会以 m 为新 n、以“上一轮的 n”为新 o,递归进入下一层 fun。
推导步骤如下:
立即学习“Java免费学习笔记(深入)”;
- var a = fun(0); → 执行 fun(0, undefined),输出 undefined;返回对象,其 fun 方法闭包保存了 n = 0
- a.fun(1); → 调用 fun(1, 0),输出 0
- a.fun(2); → 再次调用 fun(2, 0),输出 0
- a.fun(3); → 同样调用 fun(3, 0),输出 0
- var b = fun(0).fun(1).fun(2).fun(3); → 连续调用:fun(0,undefined)→输出 undefined;fun(1,0)→输出 0;fun(2,1)→输出 1;fun(3,2)→输出 2
- var c = fun(0).fun(1); c.fun(2); c.fun(3); → 前两步:fun(0,undefined)→undefined,fun(1,0)→0;c 的 fun 方法闭包保存的是 n = 1,所以 c.fun(2) 输出 1,c.fun(3) 也输出 1
警惕 var 循环 + setTimeout 的陷阱
经典代码:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
输出是 3 3 3,原因不是“setTimeout 异步”,而是:var 声明没有块级作用域,整个循环共用一个 i 变量;循环结束时 i === 3;所有定时器回调执行时,访问的都是这个最终值。
修复本质是制造独立作用域,让每个回调“记住自己的 i”:
- 用 let 替代 var → 块级绑定,每次迭代生成新绑定
- 用立即执行函数包裹:(function(i) { setTimeout(() => console.log(i), 100); })(i) → 闭包捕获当次 i 的值
- 用 setTimeout 第三个参数传参:setTimeout(console.log, 100, i)
从执行上下文角度验证结果
每轮 fun 调用都会创建独立的执行上下文(含自己的 AO/VO),其中 n 和 o 是独立存储的。返回的内部函数通过[[Environment]]引用对应外层上下文,因此不同调用链上的闭包互不干扰。这就是为什么 a.fun(1)、a.fun(2) 都输出 0(它们共享同一个外层 n=0),而 b 链式调用中每个 fun 都基于前一步的 n 更新,形成递进输出。
不复杂但容易忽略


















