题目
使用 Proxy 实现支持撤销/重做的不可变状态管理器
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
Proxy 高级应用, 不可变数据模式, 命令模式, 状态管理, 内存优化
快速回答
实现要点:
- 使用 Proxy 拦截 set/deleteProperty 操作
- 采用命令模式记录操作历史(undoStack/redoStack)
- 结合 Reflect 执行默认操作
- 使用深拷贝或结构共享保证不可变性
- 实现撤销/重做时的状态回滚机制
核心原理
通过 Proxy 创建状态对象的拦截层,在修改操作时:
- 捕获变更操作(set/deleteProperty)
- 记录当前状态快照和逆操作指令
- 使用不可变方式更新状态
- 管理两个栈(undoStack/redoStack)实现历史追踪
代码实现
class StateManager {
constructor(initialState) {
this.undoStack = [];
this.redoStack = [];
this._state = initialState;
this.state = new Proxy(initialState, {
set: (target, prop, value, receiver) => {
const prevValue = target[prop];
const success = Reflect.set(target, prop, value, receiver);
if (success && !this.isUndoingRedoing) {
// 记录操作历史
this.undoStack.push({
type: 'set',
prop,
prevValue: deepClone(prevValue),
nextValue: deepClone(value)
});
this.redoStack = []; // 清空重做栈
}
return success;
},
deleteProperty: (target, prop) => {
const prevValue = target[prop];
const success = Reflect.deleteProperty(target, prop);
if (success && !this.isUndoingRedoing) {
this.undoStack.push({
type: 'delete',
prop,
prevValue: deepClone(prevValue)
});
this.redoStack = [];
}
return success;
}
});
}
undo() {
if (!this.undoStack.length) return;
this.isUndoingRedoing = true;
const operation = this.undoStack.pop();
switch (operation.type) {
case 'set':
// 执行逆向操作
this.redoStack.push({
type: 'set',
prop: operation.prop,
prevValue: deepClone(this.state[operation.prop]),
nextValue: operation.prevValue
});
this.state[operation.prop] = operation.prevValue;
break;
case 'delete':
this.redoStack.push({
type: 'delete',
prop: operation.prop,
prevValue: deepClone(this.state[operation.prop])
});
this.state[operation.prop] = operation.prevValue;
break;
}
this.isUndoingRedoing = false;
}
redo() {
// 类似 undo 的反向实现
}
}
// 使用示例
const manager = new StateManager({ counter: 0 });
manager.state.counter = 1; // 记录操作
manager.undo(); // 回退到 0最佳实践
- 结构共享优化:使用 Immutable.js 或 Immer 避免深拷贝性能问题
- 操作批处理:实现 transaction() 方法合并连续操作
- 内存控制:设置历史栈最大长度防止内存泄漏
- 变更通知:集成观察者模式触发 UI 更新
常见错误
| 错误类型 | 后果 | 解决方案 |
|---|---|---|
| 直接修改原状态 | 破坏不可变性导致历史状态污染 | 始终返回新对象 |
| 未处理嵌套对象 | 深层属性变更无法追踪 | 递归创建 Proxy |
| 忽略 Symbol 属性 | 特殊属性操作遗漏 | 在 Proxy 中处理 ownKeys 陷阱 |
| 循环引用处理不当 | 深拷贝时栈溢出 | 使用 WeakMap 检测循环引用 |
扩展知识
- 与 Redux 对比:Redux 需要显式 action 和 reducer,本方案实现隐式操作追踪
- CRDT 算法:分布式场景下的高级状态同步方案
- 时间旅行调试:Redux DevTools 实现原理
- Proxy 局限:无法拦截直接操作(如数组 push/pop),需配合重写数组方法
性能优化策略
// 使用结构共享的优化克隆
function optimizedClone(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
// 数组特殊处理
if (Array.isArray(obj)) return [...obj];
// 使用 Object.create 保持原型链
const clone = Object.create(Object.getPrototypeOf(obj));
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
clone[key] = obj[key];
}
}
return clone;
}