JavaScript循环绑定事件时变量传参异常的星级评分系统问题
星级评分系统事件绑定问题
我正在开发一套星级评分系统,希望为星标图片列表绑定事件监听器,实现点击星级时切换实心或空心星星的效果。但遇到如下问题:在循环中使用getElementById为列表项添加事件监听器时,变量myId在每次循环迭代中取值正确,但点击触发highlightStar函数时,传入的总是myId的最后一次循环值。硬编码参数时功能可正常运行。
相关代码
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Star Rating</title> </head> <body> <ul> <li class="myClass" id="liId113"><img src="images/starFade.png"></li> <li class="myClass" id="liId213"><img src="images/starFade.png"></li> <li class="myClass" id="liId313"><img src="images/starFade.png"></li> <li class="myClass" id="liId413"><img src="images/starFade.png"></li> <li class="myClass" id="liId513"><img src="images/starFade.png"></li> </ul> </body> <script> window.onload = setArray; function setArray() { let myArray = document.getElementsByClassName("myClass"); let l = myArray.length; for(var i = 0;i < l;i++) { myId = String(myArray[i].id); document.getElementById(myId).addEventListener("click", function () {highlightStar(myId);}); //This adds event listeners to each <li> element using the correct myId but the myId of highlightStar is the last value of MyId } } function highlightStar(myId) { firstPart = myId.slice(0,4); sequenceNo = myId.charAt(4); group = myId.slice(-2); if (sequenceNo == 5) { document.getElementById(myId).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+4+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+3+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+2+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+1+group).innerHTML = "<img src='images/starFull.png'>"; } else if (sequenceNo == 4) { document.getElementById(myId).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+5+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+3+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+2+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+1+group).innerHTML = "<img src='images/starFull.png'>"; } else if (sequenceNo == 3) { document.getElementById(myId).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+5+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+4+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+2+group).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+1+group).innerHTML = "<img src='images/starFull.png'>"; } else if (sequenceNo == 2) { document.getElementById(myId).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+5+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+4+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+3+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+1+group).innerHTML = "<img src='images/starFull.png'>"; } else if (sequenceNo == 1) { document.getElementById(myId).innerHTML = "<img src='images/starFull.png'>"; document.getElementById(firstPart+5+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+4+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+3+group).innerHTML = "<img src='images/starFade.png'>"; document.getElementById(firstPart+2+group).innerHTML = "<img src='images/starFade.png'>"; } } </script> </html>
星星样式示例
- 空白星星:

- 实心星星:

问题原因
这是闭包的变量捕获特性导致的:用var声明的变量是函数级作用域,循环里的所有事件回调函数都会共享同一个myId变量。当循环结束时,myId已经变成最后一个元素的ID,所有点击事件触发时都会使用这个最终值。另外代码里的myId未用let/var声明,变成全局变量,问题会更明显。
解决方法
方法1:用let声明变量创建独立作用域
把循环变量和myId都用let声明,让每次循环生成独立的作用域,保存当前迭代的myId值:
function setArray() { let myArray = document.getElementsByClassName("myClass"); let l = myArray.length; for(let i = 0;i < l;i++) { let myId = String(myArray[i].id); document.getElementById(myId).addEventListener("click", function () { highlightStar(myId); }); } }
方法2:通过事件对象获取当前元素ID
完全不需要传递myId,直接利用this或event.currentTarget获取点击的元素,再提取ID,代码更简洁:
function setArray() { let myArray = document.getElementsByClassName("myClass"); let l = myArray.length; for(let i = 0;i < l;i++) { myArray[i].addEventListener("click", function () { highlightStar(this.id); }); } }
方法3:立即执行函数(IIFE)兼容旧浏览器
如果需要兼容不支持let的旧浏览器,用立即执行函数为每个循环迭代创建独立作用域:
function setArray() { let myArray = document.getElementsByClassName("myClass"); let l = myArray.length; for(var i = 0;i < l;i++) { let myId = String(myArray[i].id); (function(id) { document.getElementById(id).addEventListener("click", function () { highlightStar(id); }); })(myId); } }
内容的提问来源于stack exchange,提问作者user1917196
相关产品推荐
相关产品推荐

