实现撤销和重做功能的关键是将操作封装为命令对象,通过历史栈管理执行与反向逻辑。1. 定义命令类如SetTextCommand,保存执行前后的状态;2. 创建CommandManager管理undo和redo栈,执行命令时压入undo栈并清空redo栈;3. 调用undo时将命令从undo栈弹出,执行反操作后压入redo栈;4. redo则反向操作,恢复已撤销的命令。示例中文本编辑器通过该模式实现内容修改、撤销与重做。扩展可支持复合命令、限制栈大小、添加命令描述等。核心在于动作对象化与栈的顺序控制,注意清空redo栈的时机以保证操作一致性。
实现一个支持撤销和重做的命令模式,关键在于把每个操作封装成对象,记录执行和反向操作的逻辑,并通过历史栈管理这些命令。下面是一个简洁、实用的实现方式。
定义命令接口
每个命令需要统一结构,包含执行(execute)、撤销(undo)方法。这样能保证调用一致性。
例如,假设我们在做一个简单的文本编辑器,可以修改内容:
class SetTextCommand { constructor(editor, newText) { this.editor = editor; this.newText = newText; this.oldText = editor.text; // 保存之前的状态用于撤销 } execute() { this.editor.text = this.newText; } undo() { this.editor.text = this.oldText; } }
创建命令管理器
命令管理器负责执行命令,并维护撤销和重做栈。
class CommandManager { 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); } }
使用示例
将命令模式应用到具体场景中:
立即学习“Java免费学习笔记(深入)”;
const editor = { text: "" }; const commandManager = new CommandManager(); // 修改文本 commandManager.execute(new SetTextCommand(editor, "Hello")); console.log(editor.text); // 输出: Hello commandManager.execute(new SetTextCommand(editor, "Hello World")); console.log(editor.text); // 输出: Hello World // 撤销 commandManager.undo(); console.log(editor.text); // 输出: Hello // 重做 commandManager.redo(); console.log(editor.text); // 输出: Hello World
扩展建议
实际项目中可进一步优化:
- 支持批量命令(CompositeCommand),把多个命令组合成一个可撤销的操作
- 限制历史栈大小,避免内存泄漏
- 为命令添加描述,便于实现 ui 操作历史列表
- 异步命令需额外处理,比如保存状态快照或使用回调机制
基本上就这些。核心是把“动作”变成对象,控制执行流程,再用栈管理顺序。不复杂但容易忽略细节,比如清空重做栈的时机。