循环创建按钮点击函数时始终获取相同变量值的JS问题
我正在开发一个JavaScript函数,用于基于数据创建弹窗。result中的具体数据无关紧要,但因后续问题需要仍予保留。需求是点击对应按钮时,将result数组的一部分传递给弹窗。
最初尝试用v_popup["dataarray"] = result.slice(counter, indices[i] + counter);,但弹窗中数组始终为空;接着尝试创建var sub_array = result.slice(counter, indices[i] + counter);,结果同样为空;最后尝试将整个数组传给弹窗并设置low和high变量,但每个弹窗中的limit始终为93和94。
在var high = indices[i] + counter;处断点时,low和high能存储正确的递增限值,但在onclick函数内的v_popup["high"] = high;处断点,low和high始终为93和94。
疑问如下:
- 是否所有按钮共用同一个函数?
- 函数是否在点击按钮时才创建而非循环运行时?
- 是否需要为每个按钮实例设置不同变量,如
v_popup["dataarray1"]、v_popup["dataarray2"]等? - 如何直接复制切片数组到变量以无需设置限值?
- 另外,关闭弹窗后点击按钮可正常运行,但弹窗保持打开时
window["dataarray"]会变为undefined,即使每次点击都执行了v_popup["dataarray"] = result,这是为何?该如何修复?
附代码:
function addTable(result, indices) { counter = 0; indices = [2,3,6,2,4,4,1,5,3,1,1,1,2,4,12,1,4,4,2,1,4,1,12,13,1,]; //for testing for (var i = 0; i < indices.length; i++) { let btn = document.createElement("button"); btn.innerHTML = result[counter][0]; btn.className = "button-28"; var low = counter; var high = indices[i] + counter; btn.onclick = function () { var v_popup = window.open("popup.html", 'popUpWindow', 'height=300,width=700,left=50,top=50,resizable=no,scrollbars=no,toolbar=no,menubar=no,location=no,directories=no, status=no') v_popup["dataarray"] = result; v_popup["low"] = low; v_popup["high"] = high; var sub_array = result.slice(counter, indices[i] + counter); //for test } counter += indices[i]; } }
核心问题:闭包变量绑定异常
你遇到的low、high始终是最后一组值的问题,根源是循环里用var声明的变量没有块级作用域,会被所有闭包共享。循环结束后,所有onclick函数引用的都是同一个low、high、counter、i变量,它们的值已经变成循环最后的结果(也就是93、94这类值)。
闭包问题修复
把循环内的var替换成let,let有块级作用域,每次循环都会创建新的变量实例,onclick函数会绑定当前循环迭代的变量值:
function addTable(result, indices) { let counter = 0; // 替换var为let,避免全局污染 indices = [2,3,6,2,4,4,1,5,3,1,1,1,2,4,12,1,4,4,2,1,4,1,12,13,1,]; //for testing for (let i = 0; i < indices.length; i++) { // i也用let声明 let btn = document.createElement("button"); btn.innerHTML = result[counter][0]; btn.className = "button-28"; let low = counter; let high = indices[i] + counter; btn.onclick = function () { // ...后续代码 } counter += indices[i]; } }
直接传递切片数组的方案
不用给弹窗传整个数组加low/high,可以直接在循环时处理好当前按钮需要的切片数组,绑定到点击逻辑里:
btn.onclick = function () { const subArray = result.slice(low, high); const v_popup = window.open("popup.html", `popUpWindow-${i}`, 'height=300,width=700,left=50,top=50,resizable=no,scrollbars=no,toolbar=no,menubar=no,location=no,directories=no, status=no'); v_popup.dataarray = subArray; }
这样弹窗里直接用window.dataarray就能拿到对应切片,不需要再计算范围。
弹窗重复打开时dataarray变undefined的问题
因为你每次点击都用同一个窗口名popUpWindow打开弹窗,如果弹窗已打开,window.open会复用这个窗口并重新加载页面,之前设置的dataarray会被清空。
修复方案有两种:
- 给每个弹窗设置唯一窗口名,比如用循环的
i值拼接:
const v_popup = window.open("popup.html", `popUpWindow-${i}`, 'height=300,width=700,left=50,top=50,resizable=no,scrollbars=no,toolbar=no,menubar=no,location=no,directories=no, status=no');
每次点击都会打开新窗口,不会复用旧窗口导致数据丢失。
- 检查弹窗状态,已打开则直接更新数据:
btn.onclick = function () { let v_popup = window.open('', `popUpWindow-${i}`); if (!v_popup || v_popup.closed) { v_popup = window.open("popup.html", `popUpWindow-${i}`, 'height=300,width=700,left=50,top=50,resizable=no,scrollbars=no,toolbar=no,menubar=no,location=no,directories=no, status=no'); // 页面加载完成后设置数据 v_popup.onload = function() { v_popup.dataarray = result.slice(low, high); }; } else { // 弹窗已打开,直接更新数据并调至前台 v_popup.dataarray = result.slice(low, high); v_popup.focus(); } }
疑问解答
- 是否所有按钮共用同一个函数?:循环里每次都会创建新的
onclick函数,但这些函数最初共享同一个外部作用域的变量(因为var的问题)。改成let后,每个函数绑定的是当前迭代的变量实例。 - 函数是否在点击按钮时才创建而非循环运行时?:函数是在循环运行时创建的,点击时只是执行这个预先创建好的函数。
- 是否需要为每个按钮实例设置不同变量?:不需要,只要每个弹窗的窗口名唯一,或者在弹窗加载时传递对应的数据,就可以避免变量冲突。用切片数组方案,每个弹窗拿到的都是自己需要的那部分数据。
- 如何直接复制切片数组到变量?:用
result.slice(low, high)预先处理好切片,直接赋值给弹窗的dataarray即可,如前面的代码示例。
内容的提问来源于stack exchange,提问作者Mich

