类元素点击事件监听器未触发,求循环添加监听器的正确方法
问题分析与解决方法
你遇到的核心问题是遍历getElementsByClassName返回的集合时用错了方法——getElementsByClassName返回的是HTMLCollection(类数组对象),用for...in遍历它会拿到集合的索引、length等属性,而不是实际的DOM元素,自然无法给元素绑定点击事件。
下面是修正后的完整代码,我会标注关键修改点:
/* 替代重复书写document.get...的简便函数 */ function getById(id) { return document.getElementById(id) } function getByClass(c) { return document.getElementsByClassName(c) } /* 随机数计算 */ function random(xOrY) { return Math.floor(Math.random() * (+xOrY - +1)) + 1 } /* 创建网格元素 */ function createGrid(isHiding) { var grid = document.createElement("div") grid.className = isHiding ? "hiding" : "grid" return grid } /* 设置所需配置项 */ var settings = { hiding: 4, x: 6, y: 6, maxAttempts: 6 * 6, container: 'grid-container' } /* 初始化游戏对象 */ var game = { settings: settings, attempts: 0, numberFound: 0, hidingGrids: [] } /* 生成隐藏网格的坐标 */ for (i = 1; i <= game.settings.hiding; i++) { game.hidingGrids.push({ x: random(game.settings.x), y: random(game.settings.y) }) } /* 生成网格元素 */ for (y = 1; y <= game.settings.y; y++) { for (x = 1; x <= game.settings.x; x++) { var gridHasHid = false game.hidingGrids.forEach(function(grid) { if (y == grid.y && x == grid.x) { gridHasHid = true /* 创建隐藏网格 */ getById(game.settings.container).appendChild(createGrid(true)) } }) if (!gridHasHid) { /** *如果走到这里,说明该网格不是隐藏网格 * 因此需要添加标准网格。 */ getById(game.settings.container).appendChild(createGrid(false)) } } /* 添加换行符以开启下一行 */ var br = document.createElement("br") getById(game.settings.container).appendChild(br) } /* 为普通网格设置点击事件处理程序 - 关键修改:用普通for循环遍历HTMLCollection */ var gridElements = getByClass("grid"); for (var i = 0; i < gridElements.length; i++) { gridElements[i].onclick = function() { /* 需要先检查游戏是否已结束 */ if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) { alert("游戏已结束。") return } /* 通过颜色检查该格子是否已被点击过 */ if (this.style.background == "red") return /* 若走到这里,说明所有检查通过,更新颜色并提示 */ this.style.background = "red" alert("错误,你还剩 " + (game.settings.maxAttempts - ++game.attempts) + " 次尝试机会。") } } /* 为隐藏网格设置点击事件处理程序 - 同样用普通for循环 */ var hidingElements = getByClass("hiding"); for (var i = 0; i < hidingElements.length; i++) { hidingElements[i].onclick = function() { /* 需要先检查游戏是否已结束 */ if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) { alert("游戏已结束。") return } /* 通过颜色检查该格子是否已被点击过 */ if (this.style.background == "blue") return /* 若走到这里,说明所有检查通过,更新颜色并提示 */ this.style.background = "blue" game.numberFound++; // 补上原代码遗漏的计数逻辑 alert("正确,你还剩 " + (game.settings.maxAttempts - ++game.attempts) + " 次尝试机会。") } }
#grid-container { display: inline-block; width: 100%; } .grid { display: inline-block; background-color: #000; padding: 5%; margin: 2%; cursor: pointer; /* 增加鼠标指针提示可点击 */ } .hiding { background-color: #000; display: inline-block; padding: 5%; margin: 2%; cursor: pointer; } /* 取消注释下方代码可查看隐藏位置(调试用) */ /* .hiding { background-color: blue; } */
<div id="grid-container"></div>
关键修改说明:
- 替换遍历方式:把
for...in改成普通for循环,直接通过索引访问HTMLCollection里的DOM元素,确保事件绑定到正确的元素上。 - 修复逻辑漏洞:原代码中找到隐藏格子后没有增加
game.numberFound计数,导致游戏结束条件永远不满足,现已补上。 - 优化提示信息:将提示的“已用次数”调整为“剩余次数”,更符合玩家直观感受。
- 添加交互提示:给网格元素添加
cursor: pointer样式,让用户明确知道这些元素可点击。
另外,推荐一种更高效的事件委托方案——不用给每个元素单独绑定事件,而是把事件绑定到父容器上,通过判断目标元素的类名处理逻辑,后续动态添加元素也能生效:
// 用事件委托替代逐个绑定事件 getById("grid-container").addEventListener("click", function(e) { const target = e.target; // 检查游戏是否已结束 if (game.attempts == game.settings.maxAttempts || game.numberFound == game.settings.hiding) { alert("游戏已结束。"); return; } if (target.classList.contains("grid")) { if (target.style.background === "red") return; target.style.background = "red"; alert("错误,你还剩 " + (game.settings.maxAttempts - ++game.attempts) + " 次尝试机会。"); } else if (target.classList.contains("hiding")) { if (target.style.background === "blue") return; target.style.background = "blue"; game.numberFound++; alert("正确,你还剩 " + (game.settings.maxAttempts - ++game.attempts) + " 次尝试机会。"); } });
这种方式只需要绑定一次事件,性能更优,尤其适合网格数量较多的场景。
内容的提问来源于stack exchange,提问作者Jaquarh
相关产品推荐
相关产品推荐

