proxy可通过set、deleteproperty、defineproperty等trap拦截赋值、删除、定义属性等操作,抛出错误实现运行时不可变校验;需配合object.freeze及递归代理以保障深度不可变性。

Proxy 可以拦截对象的写操作,在赋值、删除、添加属性等关键环节抛出错误,从而实现不可变数据的运行时校验。
拦截所有可能修改数据的操作
不可变性要求禁止任何改变原始状态的行为。Proxy 需覆盖以下 trap:
- set:拦截属性赋值(包括新增和修改)
- deleteProperty:拦截 delete 操作
- defineProperty:拦截 Object.defineProperty 调用
- setPrototypeOf:拦截原型变更(如 Object.setPrototypeOf)
- preventExtensions:拦截 Object.preventExtensions 调用(虽然本身是冻结操作,但需防止被绕过)
基础不可变 Proxy 封装
一个最小可用的只读代理示例:
function makeImmutable(obj) {
return new Proxy(obj, {
set(target, key, value, receiver) {
throw new TypeError(`Cannot assign to read-only property '${key}'`);
},
deleteProperty(target, key) {
throw new TypeError(`Cannot delete property '${key}'`);
},
defineProperty(target, key, descriptor) {
throw new TypeError(`Cannot define property '${key}'`);
},
setPrototypeOf(target, proto) {
throw new TypeError('Cannot set prototype of immutable object');
},
preventExtensions() {
return true; // 已不可扩展,允许该调用但不生效
}
});
}
使用时:注意它只冻结第一层,嵌套对象仍可修改,如需深度冻结需递归包装或结合 Object.freeze。
OpenJDK 24 Windows x64 官方参考实现(24+36),适合 Windows 环境进行 Java 开发、编译、运行和版本兼容性验证。
处理数组与特殊行为
数组的 push/pop/shift 等方法会触发 set 和 length 修改,Proxy 默认无法直接拦截这些方法调用,但可通过以下方式增强:
- 对数组方法做包装:重写 Array.prototype 上的方法(不推荐污染原型)
- 更稳妥的方式:在 set 中检查 key 是否为数字索引或 'length',并拒绝变更
- 拦截 get:对数组方法返回的副本(如 slice())或只读包装,避免原地修改
例如,在 set 中加判断:if (key === 'length' || /^\d+$/.test(key)) throw ...
与 Object.freeze 的区别与配合
Object.freeze 只冻结自有属性,且不拦截 setPrototypeOf、defineProperty 等操作;Proxy 可捕获全部行为,但不自动冻结嵌套对象。实际中常组合使用:
- 先用 Object.freeze 冻结原始对象(防止非 Proxy 访问路径绕过)
- 再用 Proxy 拦截所有 trap,提供清晰错误提示
- 对嵌套结构,递归地对每个对象/数组应用相同逻辑(注意循环引用)
这样既利用 freeze 的底层优化,又通过 Proxy 提供可定制、可调试的不可变保障。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










