为什么for循环用let声明变量无需IIFE就能保留迭代值?底层逻辑是怎样的?
核心遗漏概念:for循环中let的特殊迭代绑定规则
你理解的let提供块级作用域是基础特性,但ES规范为for循环头部的let声明做了额外的特殊处理:
每次循环迭代都会单独创建一个全新的变量副本,将上一轮迭代的变量值赋值给新副本后,再执行本轮循环体代码。你在循环体内创建的点击事件闭包,捕获的是当前迭代对应的独立变量副本,因此互相不影响,和IIFE手动创建函数作用域保存值的效果完全一致。
而你自己写的测试代码中,变量i是在外层作用域声明的唯一实例,两个点击事件的闭包捕获的是同一个i的引用,而不是独立的副本。等到点击事件触发执行时,i的值已经被修改为2,因此两个点击回调都会输出2。
两个场景的本质差异
for(let j;;)场景:每轮迭代对应独立的j变量实例,多个闭包分别捕获不同的实例,值互不干扰- 你的测试代码场景:全程只有1个
i变量实例,所有闭包捕获同一个引用,值会跟随外层修改同步变化
实现你预期效果的两种方案
方案1:手动用块级作用域创建独立变量副本
借助let的块级作用域特性,给每个回调分别绑定独立的变量:
let div1 = document.querySelector('#div1'); let div2 = document.querySelector('#div2'); { let i = 1; div1.onclick = function (event) { console.log(i); } } { let i = 2; div2.onclick = function (event) { console.log(i); } }
方案2:沿用IIFE的传值逻辑
和你看到的var循环兼容方案逻辑一致,通过立即执行函数的参数传值,创建独立的变量副本:
let div1 = document.querySelector('#div1'); let div2 = document.querySelector('#div2'); let i = 1; div1.onclick = (function(catchI) { return function(event) { console.log(catchI); } })(i); i++; div2.onclick = (function(catchI) { return function(event) { console.log(catchI); } })(i);
其他编程语言的类似实现
绝大多数支持块级作用域和闭包/匿名函数的编程语言(比如C++、Java、Go、Rust)都有同类逻辑:循环体内的局部变量每次迭代都会创建新的实例,匿名函数捕获的是当前迭代的独立实例,不会出现旧版JS中var循环的经典闭包问题。
内容的提问来源于stack exchange,提问作者kankan256
相关产品推荐
相关产品推荐

