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

使用模块设计模式开发井字棋 重启触发removeChild类型错误求助

问题根因

  • 首先是点击事件重复绑定:你把gameFlow函数放在setInterval中周期性执行,每次执行都会给所有.gameBox元素追加绑定一次点击事件。玩家点击格子时,会同时触发多次绑定的回调,同一个格子会被插入多个X/O的<p>元素。
  • 其次是重启清节点逻辑缺校验:执行el.removeChild(el.firstChild)时,如果某个.gameBox没有子节点,el.firstChild返回值为null,传入removeChild就会触发你遇到的参数类型错误;就算有子节点,你只删除第一个子节点,重复绑定插入的多个节点残留,就会出现二次重启后部分元素清不掉的问题。

修复方案

1. 修复事件重复绑定问题

给gameFlow加绑定标记,确保事件仅绑定一次即可:

// 全局新增绑定状态标记
let isEventBinded = false
const gameFlow = () => {
    const board = gameBoard.getBoard()
    const box = document.querySelectorAll('.gameBox')
    let x = true // Put this in the global scope if there is an error.

    const renderX = (e) => {
        for (let i = 0; i < board.length; i++) {
            const target = e.target
            const data = target.getAttribute('data-index')
            const el = document.createElement('p')
            el.classList.add('elstyle')
            if (board[i] === '') {
                if (i === Number(data)) {
                    gameBoard.fillBoard(i, 1, '❌')
                    el.textContent = `${board[i]}`
                    target.appendChild(el)
                    x = false
                } 
            }
        }
    }

    const renderO = (e) => {
        for (let i = 0; i < board.length; i++) {
            const target = e.target
            const data = target.getAttribute('data-index')
            const el = document.createElement('p')
            el.classList.add('elstyle')
            if (board[i] === '') {
                if (i === Number(data)) {
                    gameBoard.fillBoard(i, 1, '⭕')
                    el.textContent = `${board[i]}`
                    target.appendChild(el)
                    x = true
                } 
            }
        }
    }
    // 仅首次执行时绑定事件
    if (!isEventBinded) {
        box.forEach(el => el.addEventListener('click', ((e) => {
            if (x) {
                renderX(e)
            } else {
                renderO(e)
            }
        })))
        isEventBinded = true
    }
}

如果需要每次重启重置下棋方x的状态,把x变量移到gameFlow外层维护即可。

2. 优化重启清空节点逻辑

直接清空元素所有内容,避免判断节点和漏删的问题:

const restartGame = (() => {
    const mainThree = document.getElementById('mainThree')
    const restart = document.getElementById('restartBtn')
    const boxes = document.querySelectorAll('.gameBox')

    restart.addEventListener('click', () => {
        mainThree.style.display = 'none'
        gameBoard.setBoard()
        boxes.forEach(el => {
            // 直接清空所有子内容,简单稳妥
            el.innerHTML = ''
        })
    })
})()

额外优化建议

gameFlow里只有胜负检测、状态同步类逻辑需要定时执行,事件绑定逻辑完全不需要周期性运行,建议把事件绑定单独抽出来在页面初始化时执行一次即可,不需要放在setInterval中,减少不必要的性能开销。

内容的提问来源于stack exchange,提问作者Rabten

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 16:18:01