关键在于用 setprototypeof 动态挂载共享撤销原型 undoable,使命令对象复用 undo 行为;命令实例须自行保存完整上下文快照,原型仅提供统一接口,不管理状态。

用 setPrototypeOf 配合命令模式实现撤销功能,关键不在于“完美”,而在于让命令对象能动态继承撤销逻辑,同时保持历史栈干净、可预测。核心是利用原型链注入通用的 undo 行为,而非每个命令重复写撤销逻辑。
命令类需统一继承可撤销原型
不要在每个命令类里手动实现 undo,而是定义一个共享的撤销原型对象,用 setPrototypeOf 动态挂载:
- 创建一个基础撤销原型:
const Undoable = { undo() { throw new Error('未实现 undo'); } }; - 执行命令后,立即用
Object.setPrototypeOf(command, Undoable)绑定原型 - 后续所有命令实例都共享该原型上的
undo方法(可被子类覆盖或增强)
命令对象必须保存足够上下文
setPrototypeOf 只解决行为复用,撤销动作能否生效,取决于命令自身是否记录了必要状态快照:
- 编辑文本命令应保存
prevText和光标位置,而非只存新值 - 移动元素命令应保存原始
left/top,而非仅目标坐标 - 避免在
execute中直接修改全局状态,优先通过闭包捕获执行前快照
历史栈管理要隔离原型与实例数据
撤销时调用 command.undo(),但原型方法不能依赖 this 上的临时属性——因为 setPrototypeOf 不改变已有属性,只影响方法查找链:
- 确保每个命令实例在构造时就完整初始化所有撤销所需字段(如
this.oldValue) - 撤销原型里的
undo方法只读取实例自有属性,不尝试写入或缓存 - 不要在原型上存状态(如
Undoable.lastExecuted),会引发多命令干扰
撤销/重做需同步更新原型链(可选进阶)
如果某些命令支持“条件撤销”(如仅当编辑未被覆盖时才可撤),可在 undo 执行后动态切换原型:
- 执行
undo后,调用Object.setPrototypeOf(command, Redoable)激活重做能力 - 重做时再切回
Undoable原型,形成闭环 - 注意:频繁切换原型可能影响性能,建议仅在明确需要状态机语义时使用
不复杂但容易忽略:setPrototypeOf 是运行时干预,它让命令对象“看起来像有 undo”,但真正可靠的是命令自己带什么数据、怎么用这些数据还原。原型只是统一接口的胶水,不是魔法。











