JavaScript开发国际象棋:setTimeout实现Pawn移动遇递归错误求助
递归错误原因分析与代码优化方案
递归错误的核心原因
- setTimeout参数顺序完全错误:你写的
setTimeout(10, pawnMoveFunc(this))会直接立即执行pawnMoveFunc(this),然后把返回值(undefined)传给setTimeout作为回调,延迟10ms执行undefined等于无效延迟。正确顺序是setTimeout(回调函数, 延迟时间, 回调参数)。 - 同步递归无延迟:
pawnMoveFunc的else分支里直接调用pawnMoveFunc(piece),没有包裹setTimeout,这会导致同步递归,瞬间填满调用栈,触发栈溢出错误。 - stopLoop逻辑无效:
move函数里stopLoop = true; stopLoop = false;等于没设置,而且持续循环调用所有Pawn的move方法,会重复触发递归逻辑,加重栈溢出问题。
修复后的核心代码
修正pawnMoveFunc(加入延迟递归)
function pawnMoveFunc(piece) { console.log(colourToMove, mouse.x, mouse.y); let actualPawnX = piece.x - piece.offset; if (piece.colour !== colourToMove) { console.log('wrong colour'); return; // 提前退出,减少嵌套层级 } if (mouse.x !== actualPawnX || mouse.y !== piece.y) { piece.x = mouse.x + piece.offset; piece.y = mouse.y; drawAfterMove(); } // 仅在需要继续监听且stopLoop为false时,延迟递归调用 if (mouse.x === actualPawnX && mouse.y === piece.y && !stopLoop) { setTimeout(pawnMoveFunc, 10, piece); // 用setTimeout包裹,避免同步递归 } }
修正Pawn类的move方法(修复setTimeout参数)
class Pawn{ constructor(colour, x, y) { // 简化构造函数的条件判断 const imgId = colour === 'white' ? 'whitePawn' : 'blackPawn'; this.img = document.getElementById(imgId); this.colour = colour; this.offset = 7.5; this.x = x * tileSize + this.offset; this.y = y * tileSize; this.width = 35; this.height = 50; this.isDragging = false; // 新增拖拽状态标志,替代全局stopLoop的部分作用 } draw() { ctx.drawImage(this.img, this.x, this.y, this.width, this.height); } move() { let actualPawnX = this.x - this.offset; // 仅当鼠标在棋子上且未拖拽时,启动递归监听 if (mouse.x === actualPawnX && mouse.y === this.y && !this.isDragging) { this.isDragging = true; // 标记为拖拽中,避免重复触发 setTimeout(pawnMoveFunc, 10, this); } // 移除无效的stopLoop反复赋值 } // 新增停止拖拽的方法,在mouseup事件中调用 stopDrag() { this.isDragging = false; stopLoop = true; } }
进一步优化建议
- 减少全局变量依赖:把
colourToMove、mouse、stopLoop这些全局变量改成参数传入,或者封装到一个游戏管理类里,避免全局污染和意外修改。 - 用鼠标事件替代持续循环调用move:监听
mousedown(触发棋子拖拽)、mousemove(实时更新棋子位置)、mouseup(停止拖拽)事件,比持续循环调用move更高效,也更符合前端交互逻辑。 - 添加边界检查:棋子移动时要限制在棋盘范围内,避免拖出棋盘。
- 替换setTimeout为requestAnimationFrame:动画更流畅,能和浏览器刷新帧率同步,避免不必要的延迟。
- 封装移动逻辑到类内部:把pawnMoveFunc的逻辑整合到Pawn类的方法里,让类的职责更单一,代码更易维护。
内容的提问来源于stack exchange,提问作者iamdeedz
相关产品推荐
相关产品推荐

