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

JavaScript开发国际象棋:setTimeout实现Pawn移动遇递归错误求助

递归错误原因分析与代码优化方案

递归错误的核心原因

  1. setTimeout参数顺序完全错误:你写的setTimeout(10, pawnMoveFunc(this))会直接立即执行pawnMoveFunc(this),然后把返回值(undefined)传给setTimeout作为回调,延迟10ms执行undefined等于无效延迟。正确顺序是setTimeout(回调函数, 延迟时间, 回调参数)。
  2. 同步递归无延迟:pawnMoveFunc的else分支里直接调用pawnMoveFunc(piece),没有包裹setTimeout,这会导致同步递归,瞬间填满调用栈,触发栈溢出错误。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 08:54:23