当前位置:首页 > JavaScript

js 实现撤销重做

2026-02-02 22:30:01JavaScript

撤销重做功能的实现思路

实现撤销重做功能的核心是使用命令模式结合历史记录栈。通过维护两个栈(撤销栈和重做栈),可以跟踪用户操作并支持回退或前进。

基础数据结构

需要两个栈来存储操作:

  • undoStack:存储已执行的操作,用于撤销。
  • redoStack:存储已撤销的操作,用于重做。
class HistoryManager {
  constructor() {
    this.undoStack = [];
    this.redoStack = [];
  }
}

执行操作

每次用户执行操作时,将操作封装为一个命令对象并存入undoStack,同时清空redoStack

execute(command) {
  command.execute();
  this.undoStack.push(command);
  this.redoStack = []; // 新操作会清除重做历史
}

撤销操作

undoStack弹出最近的操作,执行其撤销逻辑,并将操作存入redoStack

undo() {
  if (this.undoStack.length === 0) return;
  const command = this.undoStack.pop();
  command.undo();
  this.redoStack.push(command);
}

重做操作

redoStack弹出最近撤销的操作,重新执行并放回undoStack

redo() {
  if (this.redoStack.length === 0) return;
  const command = this.redoStack.pop();
  command.execute();
  this.undoStack.push(command);
}

命令对象示例

每个操作需要实现executeundo方法:

class TextChangeCommand {
  constructor(textField, oldText, newText) {
    this.textField = textField;
    this.oldText = oldText;
    this.newText = newText;
  }

  execute() {
    this.textField.value = this.newText;
  }

  undo() {
    this.textField.value = this.oldText;
  }
}

完整实现示例

class HistoryManager {
  constructor() {
    this.undoStack = [];
    this.redoStack = [];
  }

  execute(command) {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = [];
  }

  undo() {
    if (this.undoStack.length === 0) return;
    const command = this.undoStack.pop();
    command.undo();
    this.redoStack.push(command);
  }

  redo() {
    if (this.redoStack.length === 0) return;
    const command = this.redoStack.pop();
    command.execute();
    this.undoStack.push(command);
  }
}

// 使用示例
const history = new HistoryManager();
const input = document.getElementById('text-input');

input.addEventListener('input', (e) => {
  const command = new TextChangeCommand(
    input,
    input._lastValue || '',
    e.target.value
  );
  input._lastValue = e.target.value;
  history.execute(command);
});

document.getElementById('undo-btn').addEventListener('click', () => history.undo());
document.getElementById('redo-btn').addEventListener('click', () => history.redo());

优化方向

对于频繁操作(如文本输入),可以使用防抖来合并连续操作:

let debounceTimer;
input.addEventListener('input', (e) => {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    const command = new TextChangeCommand(
      input,
      input._lastValue || '',
      e.target.value
    );
    input._lastValue = e.target.value;
    history.execute(command);
  }, 300);
});

状态快照方案

对于复杂状态,可以采用快照模式存储完整状态:

js 实现撤销重做

class StateSnapshotCommand {
  constructor(appState, newState) {
    this.appState = appState;
    this.prevState = {...appState};
    this.newState = newState;
  }

  execute() {
    Object.assign(this.appState, this.newState);
  }

  undo() {
    Object.assign(this.appState, this.prevState);
  }
}

注意事项

  • 内存管理:大量操作历史会占用内存,需设置栈大小限制
  • 性能考量:复杂操作的序列化/反序列化可能影响性能
  • 并发控制:确保操作执行和状态变更的原子性

标签: 重做js
分享给朋友:

相关文章

js实现全选

js实现全选

实现全选功能的方法 在JavaScript中实现全选功能通常涉及监听全选复选框的点击事件,并根据其状态控制其他复选框的选中状态。以下是几种常见的实现方式: 基础DOM操作实现 通过获取所有目标复选框…

js 实现继承

js 实现继承

原型链继承 通过让子类的原型对象指向父类的实例来实现继承。子类实例可以访问父类原型上的属性和方法。 function Parent() { this.name = 'parent'; } Par…

js 实现链表

js 实现链表

链表的基本概念 链表是一种线性数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。与数组不同,链表在内存中是非连续存储的,插入和删除操作效率较高。 链表的实现 在 JavaScrip…

js实现二叉树

js实现二叉树

二叉树的基本概念 二叉树是一种树形数据结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。二叉树的常见操作包括插入、删除、遍历等。 二叉树的节点定义 在JavaScript中,二叉树的节点可…

js实现投球

js实现投球

实现投球动画的基本思路 使用JavaScript和CSS动画结合的方式模拟投球效果。核心是通过改变元素的位置、旋转和缩放属性,配合定时器或CSS过渡实现平滑动画。 创建基础HTML结构 <di…

js 实现全屏

js 实现全屏

使用 requestFullscreen 方法 通过调用元素的 requestFullscreen 方法可以实现全屏。该方法兼容现代浏览器,但不同浏览器可能需要前缀。 const element =…