You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 19:58:14