开发环境可用 Proxy 拦截 set 等操作实现不可变语义,通过 throw Error 明确报错;基础版保护单层对象,进阶版可递归代理嵌套结构;需避免与 Vue/React 响应式状态直接混用。
可以在开发环境下用 proxy 拦截所有赋值操作,对目标对象的任意属性修改都抛出明确错误,从而模拟不可变语义——既不改变原始数据,又让非法修改在调试阶段立刻暴露。
核心思路:拦截 set 并统一拒绝写入
Proxy 的 set 陷阱是实现的关键。只要在 handler 中不将 value 写入 target,且始终返回 false 或直接 throw,就能阻断所有赋值行为。
- 推荐在开发环境使用 throw new Error,信息清晰、堆栈完整,便于定位哪行代码试图修改
- 避免返回 false,因为在非严格模式下它会静默失败,不利于排查
- 务必检查 receiver === proxy,防止通过 Object.assign 等方式绕过代理(可选增强)
基础实现:单层对象保护
适用于普通扁平对象,如配置项、表单初始值等:
function makeImmutable(obj) {
if (process.env.NODE_ENV !== 'development') return obj;
return new Proxy(obj, {
set(target, key, value, receiver) {
throw new Error(`[Immutable Error] 尝试修改不可变对象的属性 "${key}"`);
},
deleteProperty() {
throw new Error('[Immutable Error] 不可删除不可变对象的属性');
},
defineProperty() {
throw new Error('[Immutable Error] 不可向不可变对象添加新属性');
}
});
}
调用 const config = makeImmutable({ apiUrl: '/api' }) 后,任何 config.apiUrl = '/test' 都会立即报错。
进阶处理:支持嵌套与数组
原生 Proxy 默认只拦截第一层。若需深层保护,可递归代理子对象和数组,但要注意循环引用和性能:
- 对 get 返回值做判断:若为对象或数组,自动包装为新的 immutable proxy
- 对数组方法如
push、pop等,需在 apply 陷阱中拦截(需代理数组本身) - 更轻量做法:仅保护顶层,配合文档约定“该对象及其后代均不可变”,靠开发规范+类型系统(如 TypeScript)辅助约束
与框架协同:避免响应式冲突
Vue/React 等框架内部依赖对象可变性实现响应更新。因此不可变 proxy 应仅用于纯数据容器,而非组件响应式状态:
- 不要把 immutable proxy 直接传给
ref()或useState() - 适合场景:API 响应缓存、路由参数快照、本地 mock 数据源
- 如需响应式 + 不可变语义,应配合结构化更新(如
set(prev => ({ ...prev, x: newVal }))),而非修改原对象


















