proxy 可通过拦截 set、deleteproperty、defineproperty 和 setprototypeof 实现真正的动态只读保护,支持浅层与深层递归代理,相比 object.freeze() 具备实时拦截与定制能力,但存在性能开销且不冻结原型链属性。

JavaScript 中 Proxy 可以通过拦截对象的赋值(set)、删除(deleteProperty)、添加属性(defineProperty)等操作,配合 get 拦截实现真正的只读保护——不是靠 Object.freeze() 那种浅层冻结,而是动态拦截、即时拒绝非法修改。
拦截关键陷阱操作
只读的核心是阻止任何改变状态的行为。Proxy 需重点拦截以下 4 个 trap:
-
set:拦截属性赋值(包括新增和修改),一律返回
false或抛错 -
deleteProperty:拦截
delete obj.key,返回false -
defineProperty:拦截
Object.defineProperty,防止配置变更 -
setPrototypeOf:拦截
Object.setPrototypeOf(),避免原型篡改
基础只读 Proxy 实现
一个最小可用版本如下(不递归代理嵌套对象):
function readonly(obj) {
return new Proxy(obj, {
set(target, key, value) {
throw new TypeError(`Cannot assign to read-only property '${key}'`);
},
deleteProperty(target, key) {
throw new TypeError(`Cannot delete property '${key}'`);
},
defineProperty(target, key, desc) {
throw new TypeError(`Cannot define property '${key}'`);
},
setPrototypeOf(target, proto) {
throw new TypeError('Cannot set prototype of a readonly object');
}
});
}
const data = { a: 1, b: { c: 2 } };
const ro = readonly(data);
ro.a = 99; // 报错
delete ro.a; // 报错
支持深层只读(递归代理)
如果嵌套对象也要只读,需在 get 拦截中对返回的引用类型(对象、数组)自动包装为只读 Proxy:
- 判断返回值是否为对象且非 null,再用
readonly()包一层 - 注意避免代理循环引用(如
obj.self = obj),可加缓存或 WeakMap 记录已代理对象 - 数组方法如
push、pop属于自身方法调用,不在 set 拦截范围内,需额外拦截apply(若代理数组)
与 Object.freeze 的区别
Object.freeze() 是静态冻结:只冻结自有属性,不响应后续新增属性,且无法拦截 in、for...in 等枚举行为;Proxy 是动态拦截:能实时响应所有操作,还可定制报错信息、日志、条件放行等逻辑。但 Proxy 有性能开销,且无法冻结原型链上的属性——只作用于被代理对象本身。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











