js 实现撤销重做
撤销重做功能的实现思路
实现撤销重做功能的核心是使用命令模式结合历史记录栈。通过维护两个栈(撤销栈和重做栈),可以跟踪用户操作并支持回退或前进。
基础数据结构
需要两个栈来存储操作:
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);
}
命令对象示例
每个操作需要实现execute和undo方法:
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);
}
}
注意事项
- 内存管理:大量操作历史会占用内存,需设置栈大小限制
- 性能考量:复杂操作的序列化/反序列化可能影响性能
- 并发控制:确保操作执行和状态变更的原子性






