开发HTML/CSS/JS井字棋时遇Uncaught TypeError错误求助
解决"Uncaught TypeError: Cannot read property 'addEventListener' of null"错误及井字棋代码优化
嘿,我一眼就看到你报错的根源了,咱们先解决最紧急的问题,再聊聊代码里可以优化的地方:
一、当前报错的直接原因
你在获取Play按钮的时候,类选择器漏掉了.前缀!
原代码里的这一行:
var playButton = document.querySelector('play-btn');
应该修改为:
var playButton = document.querySelector('.play-btn');
因为play-btn是按钮的class属性值,使用querySelector选择类元素必须以.开头,不然浏览器会误以为你要找一个叫<play-btn>的HTML标签,自然找不到元素,导致playButton的值为null,调用addEventListener就会触发你看到的错误。
二、额外的代码优化建议
解决完报错后,你的代码还有几个小问题可以调整,让游戏逻辑更顺畅:
1. 避免重复绑定点击事件
你现在每次点击Play按钮,都会循环给所有tile重新绑定一次点击事件,这会导致同一个tile被绑定多次点击逻辑,触发多次回调。我们可以把tile的事件绑定只执行一次,用游戏状态来控制是否允许点击:
// 新增一个函数,只绑定一次tile的点击事件 function bindTileEvents() { for (var i = 1; i < 10; i++) { // 使用闭包保存循环变量i的值,避免所有tile都引用最后一个i的值 (function(tileIndex) { const tile = document.querySelector('.tile-' + tileIndex); tile.addEventListener('click', function () { // 如果游戏未开始,或者tile已经被点击过,直接返回 if (!gamePlaying || this.classList.contains('clicked')) return; if (activePlayer === 1) { this.classList.add('p1-t' + tileIndex); p1Checks[tileIndex - 1] = 1; checkP1Win(); } else { this.classList.add('p2-t' + tileIndex); p2Checks[tileIndex - 1] = 1; checkP2Win(); } this.classList.add('clicked'); nextPlayer(); }); })(i); } }
然后在DOM加载完成后,只调用一次这个函数,而不是在Play按钮的点击事件里重复调用:
document.addEventListener("DOMContentLoaded", function (event) { var activePlayer, gamePlaying; var p1Checks, p2Checks; var playButton = document.querySelector('.play-btn'); init(); bindTileEvents(); // 只绑定一次tile事件 playBtn(); function playBtn() { playButton.addEventListener('click', function () { gamePlaying = true; activePlayer = 1; init(); // 点击Play时重置游戏状态 }); } // ... 其他函数保持不变 });
2. 完善初始化逻辑
你的init函数里可以加上清除clicked类,这样重新开始游戏时,所有tile都能恢复可点击状态:
function init() { gamePlaying = false; p1Checks = [0, 0, 0, 0, 0, 0, 0, 0, 0]; p2Checks = [0, 0, 0, 0, 0, 0, 0, 0, 0]; for (var i = 1; i < 10; i++) { const tile = document.querySelector('.tile-' + i); // 一次性移除多个类,代码更简洁 tile.classList.remove('player-1', 'player-2', 'p1-t' + i, 'p2-t' + i, 'clicked'); } }
3. 优化胜利后的逻辑
当前玩家胜利后只是弹出alert,但游戏还能继续点击tile,你可以在checkP1Win和checkP2Win函数里添加逻辑,胜利后把gamePlaying设为false,阻止后续点击:
function checkP1Win() { const winConditions = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ]; for (let condition of winConditions) { if (p1Checks[condition[0]] === 1 && p1Checks[condition[1]] === 1 && p1Checks[condition[2]] === 1) { alert("Player 1 wins!"); gamePlaying = false; // 胜利后停止游戏 return; } } } // checkP2Win函数同理 function checkP2Win() { const winConditions = [ [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ]; for (let condition of winConditions) { if (p2Checks[condition[0]] === 1 && p2Checks[condition[1]] === 1 && p2Checks[condition[2]] === 1) { alert("Player 2 wins!"); gamePlaying = false; // 胜利后停止游戏 return; } } }
这样玩家胜利后,tile就不会再响应点击了。
内容的提问来源于stack exchange,提问作者A.H 36
相关产品推荐
相关产品推荐

