proxy 无法直接拦截 set/map 的 add、set 等方法调用,需通过代理包装对象并重写 get 拦截器,将方法调用重定向至自定义逻辑,内部维护真实实例以实现增删改拦截与扩展功能。

JavaScript 中 Proxy 可以拦截对 Set 和 Map 的修改,但不能直接代理 Set 或 Map 实例本身——因为它们的大部分修改操作(如 add、delete、set、clear)是通过方法调用完成的,而 Proxy 默认只拦截对象属性访问,不自动捕获方法内部行为。
要有效拦截 Set/Map 的修改,核心思路是:代理其 包装对象,把所有增删改操作重定向到自定义逻辑,并在内部维护一个真实的 Set/Map 实例。
拦截 Set 修改:代理包装器 + 方法重写
Set 本身不可被 Proxy 直接有效代理(new Proxy(new Set(), {...}) 无法拦截 add() 等方法调用)。必须封装一层:
- 创建一个普通对象,将 Set 实例存为私有属性(如
_set) - 在 Proxy handler 中重写
get,把add、delete、clear、has等方法绑定到自定义逻辑 - 对每个方法执行前/后插入拦截点(例如打印、校验、通知)
示例:
const createTrackedSet = () => {
const set = new Set();
return new Proxy(
{},
{
get(target, prop) {
if (prop === 'add') {
return (value) => {
console.log('Set.add:', value);
return set.add(value);
};
}
if (prop === 'delete') {
return (value) => {
console.log('Set.delete:', value);
return set.delete(value);
};
}
if (prop === 'clear') {
return () => {
console.log('Set.clear');
set.clear();
};
}
if (prop === 'has') return (value) => set.has(value);
if (prop === 'size') return set.size;
if (prop === 'values') return () => set.values();
if (prop === 'entries') return () => set.entries();
// 其他需透传的方法(如 forEach)也需手动处理
}
}
);
};
拦截 Map 修改:同理封装 + 键值感知
Map 同样需包装。关键区别在于:Map 的操作涉及 键和值,拦截时可基于键做细粒度控制(如禁止某些 key、记录变更、触发响应式更新):
-
set(key, value)可检查 key 类型、值有效性,或拒绝重复 key -
delete(key)可记录被删的键值对 -
clear()可触发批量变更事件
示例(带简单校验):
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
const createValidatedMap = () => {
const map = new Map();
return new Proxy(
{},
{
get(target, prop) {
if (prop === 'set') {
return (key, value) => {
if (typeof key !== 'string') {
throw new Error('Map key must be string');
}
console.log('Map.set:', { key, value });
return map.set(key, value);
};
}
if (prop === 'delete') {
return (key) => {
const existed = map.has(key);
console.log('Map.delete:', key, existed ? '✓' : '×');
return map.delete(key);
};
}
if (prop === 'get') return (key) => map.get(key);
if (prop === 'has') return (key) => map.has(key);
if (prop === 'size') return map.size;
}
}
);
};
注意:不能靠 trap 拦截内置方法调用
以下写法无效:
const broken = new Proxy(new Set(), {
set() { /* 不会触发 —— Set.prototype.add 不走 [[Set]] */ }
});
原因:add 是 Set 原型上的方法,调用时 this 指向 Set 实例,Proxy 的 set trap 只对属性赋值(obj.prop = x)生效,对方法调用无感知。
真正能用的 trap(对包装对象有用)包括:get、apply(若代理函数)、ownKeys、has 等,但必须配合手动方法转发。
进阶:用 WeakMap 存储私有数据 + 支持迭代器
若需保持原生 Set/Map 的迭代能力(如 for...of),可在包装对象上实现 [Symbol.iterator] 并委托给内部实例:
- 返回
map.keys()、map.values()或map.entries()迭代器 - 用
WeakMap关联代理对象与真实实例,避免内存泄漏 - 支持
forEach、entries()等方法透传
这样既保留原生语义,又获得完全可控的拦截能力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










