JavaScript中for循环作用域原理探究:循环按钮点击值异常问题
JavaScript循环中按钮点击值异常的底层运行机制
咱们先还原一下你遇到的场景:用var声明变量i写了个for循环创建5个按钮,结果不管点哪个按钮,弹出的i都是6;但换成IIFE(立即执行函数)或者用let声明i,就能得到正确的按钮对应数值。这背后其实是JavaScript的作用域和变量绑定时机在搞鬼,咱们一步步拆解:
一、用var时出问题的核心原因
var是函数作用域的变量,在createButtons函数里,不管循环多少次,你声明的i都是同一个变量——它的作用域覆盖整个函数。
循环执行的时候,你给每个按钮绑定了onclick事件,但这个事件函数是异步触发的:只有当你点击按钮时,它才会去读取i的值。而循环的执行速度非常快,等你点击任何一个按钮的时候,循环早就跑完了,此时i已经完成了最后一次自增,变成了6(因为循环条件是i <= 5,当i=5时执行完循环体,然后i++变成6,不满足条件才退出循环)。所有按钮的onclick函数引用的都是同一个i,自然弹出的都是6。
二、IIFE为什么能解决问题?
你代码里的立即执行函数(function(num) { ... })(i),本质是给每个按钮的点击事件单独创建了一个独立的函数作用域:
- 每次循环时,我们把当前的
i作为参数传递给IIFE,这个num就是该作用域内的局部变量; - 按钮的
onclick函数会捕获这个作用域里的num,而不是外层函数的i; - 因为每次循环都会生成一个新的作用域,每个作用域里的
num保存的是当前迭代的i值,所以点击不同按钮时,读取的是各自作用域里的num,自然能得到正确的数值。
三、let解决问题的底层逻辑
ES6引入的let带来了块级作用域,而for循环的每一次迭代,都会创建一个全新的块级作用域:
- 用
let i = 1声明循环变量时,每次循环迭代都会生成一个新的i变量,并且会把上一次迭代的i值赋值给这个新变量; - 每个按钮的
onclick函数绑定的是当前迭代块里的那个i,而不是共享的同一个变量; - 当你点击按钮时,读取的是对应迭代块里的
i值,所以每个按钮都能弹出正确的数字。
代码对比回顾
有问题的var写法
function createButtons() { for (var i = 1; i <= 5; i++) { var body = document.getElementsByTagName("BODY")[0]; var button = document.createElement("BUTTON"); button.innerHTML = 'Button ' + i; // 点击时读取的是外层共享的i,此时i已经是6 button.onclick = function() { alert('This is button ' + i); }; body.appendChild(button); } }
用IIFE修复的写法(你的代码)
function createButtons() { for (var i = 1; i <= 5; i++) { var body = document.getElementsByTagName("BODY")[0]; var button = document.createElement("BUTTON"); button.innerHTML = 'Button ' + i; // 用IIFE创建独立作用域,保存当前i的值为num (function(num) { button.onclick = function() { alert('This is button ' + num); } })(i); body.appendChild(button); } }
用let简化的写法
function createButtons() { // let声明的i是块级作用域,每次迭代都有新的i for (let i = 1; i <= 5; i++) { var body = document.getElementsByTagName("BODY")[0]; var button = document.createElement("BUTTON"); button.innerHTML = 'Button ' + i; button.onclick = function() { alert('This is button ' + i); }; body.appendChild(button); } }
内容的提问来源于stack exchange,提问作者Shubham
相关产品推荐
相关产品推荐

