将JSON数据绑定到onclick函数时遇闭包问题求助
问题:循环绑定点击事件时仅最后一组元素生效
我通过AJAX获取了JSON数据,想要给每个IDOption元素绑定点击事件,实现点击时显示IDToOpen元素、隐藏IDToClose元素。但循环绑定事件时,外部console能正确获取每个元素对象,可事件内部的console只能拿到最后一组元素对象,功能无法正常工作。
相关JSON数据
{ "1": { "ONClick": "OpzioneUnoDellaDomandaUno", "apre": [ "DomandaNumeroDueDelQuestionarioUno" ], "chiude": [ "DomandaNumeroTreDelQuestionarioUno" ] }, "2": { "ONClick": "OpzioneDueDellaDomandaUno", "apre": [ "DomandaNumeroTreDelQuestionarioUno" ], "chiude": [ "DomandaNumeroDueDelQuestionarioUno" ] }, "3": { "ONClick": "OpzioneTreDellaDomandaUno", "chiude": [ "DomandaNumeroDueDelQuestionarioUno", "DomandaNumeroTreDelQuestionarioUno" ] }, "4": { "ONClick": "OpzioneQuattroDellaDomandaUno", "chiude": [ "DomandaNumeroDueDelQuestionarioUno", "DomandaNumeroTreDelQuestionarioUno" ] }, "5": { "ONClick": "OpzioneUnoDellaDomandaDue" }, "6": { "ONClick": "OpzioneDueDellaDomandaDue" } }
原实现代码
var dataJson = JSON.parse(data); //console.log('Ramification: ', dataJson); for (var key in dataJson) { if (dataJson.hasOwnProperty(key)) { var IDOption = document.getElementById(dataJson[key].ONClick); var open = document.getElementById(dataJson[key].apre); var close = document.getElementById(dataJson[key].chiude); var IDToOpen = document.getElementById(open.id); var IDToClose = document.getElementById(close.id); console.log(IDToOpen); // 这里能拿到正确的元素对象 console.log(IDToClose); // 这里能拿到正确的元素对象 IDOption.addEventListener("click", function(){ console.log(IDToOpen); // 这里只拿到最后一组的元素 console.log(IDToClose); // 这里只拿到最后一组的元素 IDToOpen.style.display = 'flex'; IDToClose.style.display = 'none'; }); } }
问题原因
这是变量作用域导致的闭包陷阱:循环里用var声明的变量是函数级作用域,所有点击事件的回调函数共享同一个IDToOpen和IDToClose变量。循环结束后,这两个变量会被最后一次循环的值覆盖,所以所有点击事件触发时,访问的都是最后一组元素。
解决方案
方法1:用let替代var声明变量
把循环内的变量声明改成let,let是块级作用域,每次循环都会创建新的变量绑定,回调函数会捕获当前循环的变量值:
var dataJson = JSON.parse(data); for (var key in dataJson) { if (dataJson.hasOwnProperty(key)) { // 用let声明,每个循环块有独立的变量 let IDOption = document.getElementById(dataJson[key].ONClick); let open = document.getElementById(dataJson[key].apre); let close = document.getElementById(dataJson[key].chiude); let IDToOpen = document.getElementById(open?.id); let IDToClose = document.getElementById(close?.id); IDOption.addEventListener("click", function(){ if(IDToOpen) IDToOpen.style.display = 'flex'; if(IDToClose) IDToClose.style.display = 'none'; }); } }
注意:加了
?.可选链操作符,避免部分数据没有apre或chiude时出现报错。
方法2:使用立即执行函数(IIFE)创建独立作用域
在循环内用立即执行函数把当前循环的变量封装起来,形成独立作用域,让回调函数绑定当前循环的变量:
var dataJson = JSON.parse(data); for (var key in dataJson) { if (dataJson.hasOwnProperty(key)) { var IDOption = document.getElementById(dataJson[key].ONClick); var open = document.getElementById(dataJson[key].apre); var close = document.getElementById(dataJson[key].chiude); var IDToOpen = document.getElementById(open?.id); var IDToClose = document.getElementById(close?.id); // 用IIFE包裹,传递当前变量 (function(toOpen, toClose) { IDOption.addEventListener("click", function(){ if(toOpen) toOpen.style.display = 'flex'; if(toClose) toClose.style.display = 'none'; }); })(IDToOpen, IDToClose); } }
方法3:改用Object.values()配合forEach遍历
把对象转成数组后用forEach遍历,forEach的回调函数本身是独立作用域,能保留每次迭代的变量:
var dataJson = JSON.parse(data); Object.values(dataJson).forEach(item => { const IDOption = document.getElementById(item.ONClick); const open = document.getElementById(item.apre); const close = document.getElementById(item.chiude); const IDToOpen = document.getElementById(open?.id); const IDToClose = document.getElementById(close?.id); IDOption.addEventListener("click", () => { if(IDToOpen) IDToOpen.style.display = 'flex'; if(IDToClose) IDToClose.style.display = 'none'; }); });
内容的提问来源于stack exchange,提问作者Aogiri14
相关产品推荐
相关产品推荐

