当前位置:首页 > JavaScript

js 实现撤销重做

2026-02-02 22:30:01JavaScript

撤销重做功能的实现思路

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

基础数据结构

需要两个栈来存储操作:

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

执行操作

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

js 实现撤销重做

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

js 实现撤销重做

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);
});

状态快照方案

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

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 功能的方法 在 JavaScript 中实现特定功能通常涉及多个步骤。以下是常见的实现方法: 基础语法和变量声明 使用 let 或 const 声明变量: let co…

js实现日历

js实现日历

实现日历的基本思路 使用JavaScript实现日历的核心是动态生成日期表格,并处理月份切换逻辑。需要计算当前月的天数、起始星期几,并动态渲染到页面上。 获取当前日期信息 通过Date对象获取当前年…

js实现抽奖

js实现抽奖

实现抽奖功能的基本思路 抽奖功能的核心是随机选择奖项并展示结果。可以通过数组存储奖项,利用随机数生成索引,最后通过动画增强用户体验。 准备奖项数据 定义一个数组存储奖项信息,每个奖项可以包含名称、图…

节流js实现

节流js实现

节流(Throttle)的实现原理 节流是一种限制函数执行频率的技术,确保函数在一定时间间隔内最多执行一次。适用于高频触发事件(如滚动、输入、窗口调整等)的场景。 基础实现方式 使用时间戳判断是否执…

js节流实现

js节流实现

节流的概念 节流(Throttle)是一种限制函数执行频率的技术,确保函数在一定时间间隔内只执行一次。常用于滚动事件、窗口调整等高频触发的场景。 基础实现方法 使用时间戳判断是否执行函数: fun…

js实现正交

js实现正交

正交的概念 正交在数学和计算机科学中通常指两个向量垂直或线性无关。在编程中,正交性常被用于设计模块化、低耦合的系统。 向量正交判断 判断两个向量是否正交可以通过点积是否为0来实现: functio…