先说结论:两者不相背,甚至经常配合出现
1. 它们本质不同
- 暂时性死区 (TDZ)
是一个时间阶段 的限制:在let/const声明之前,变量不可访问。防止你在未初始化时就用它。 - 闭包
是一个空间关系的能力:函数能记住并访问它被创建时所在的作用域里的变量。让你在函数执行上下文销毁后,还能用那些变量。
2. 经典结合案例:用 TDZ 创造闭包陷阱
循环绑定事件是最经典的"闭包 + TDZ"交互场景。
如果 TDZ 和闭包相背,这段代码就该报错,但实际上它能运行(只是结果不符合预期):
js
function badLoop() {
// let i = 0; // 如果用 let,表现会完全不一样
for (var i = 0; i < 3; i++) {
setTimeout(function() {
// 这是一个闭包:它访问了外部作用域的变量 i
console.log(i);
}, 0);
}
}
badLoop(); // 输出 3, 3, 3
闭包视角 :setTimeout 回调记住了变量 i,而不是它的值。
TDZ 视角:
- 用
var:没有 TDZ,i被提升为函数级变量,循环结束后是 3,所有闭包共享同一个i。 - 用
let:有 TDZ,但每次迭代都有独立的词法作用域,i在每次迭代的 TDZ 之后被初始化为不同的值。这恰好修正了闭包的问题。
js
function goodLoop() {
for (let i = 0; i < 3; i++) { // i 有块级作用域
setTimeout(function() {
console.log(i);
}, 0);
}
}
goodLoop(); // 输出 0, 1, 2
这里 TDZ 的存在确保了变量必须先声明后使用 ,而 let 的循环特性创造了三个独立的绑定 ,让闭包捕获到了不同的值。它们在这里是协作关系。
3. 也可能互相"阻挡"
闭包让你能"看到"外部变量,TDZ 又让你暂时"不能摸"它,就会产生错误:
js
function test() {
// 这是一个闭包,它想访问 TDZ 里的变量
const show = () => console.log(myName);
show(); // ❌ ReferenceError: Cannot access 'myName' before initialization
const myName = 'TDZ';
}
test();
这里,show 这个闭包想用 myName,但执行时它还在 TDZ 里,所以报错。这是TDZ 阻止了闭包在合法时机前访问变量 ,而不是它们相背。如果相背,闭包就不该能看见这个变量,但实际是看得见,不许动。
总结
- 闭包是空间概念:让你跨作用域访问变量。
- TDZ 是时间概念:让你只能在变量初始化后访问它。
它们之间不存在根本矛盾,更像是:闭包锁定了变量引用,TDZ 则给这个引用加上了一个"时间锁",必须在正确的时间点才能使用。