javascript proxy是什么_如何拦截对象操作【教程】

Proxy 是对象的拦截代理层,创建新对象拦截对它的操作而非包装原对象;必须掌握 get/set/has/ownKeys 四个基础 trap,代理数组时需特别处理 length 和原型方法,且注意性能与 IE 兼容性问题。

Proxy 是什么:不是包装器,是对象的拦截代理层

Proxy 不是给对象加一层“壳”,而是创建一个新对象,所有对它的访问(读、写、枚举、构造等)都会被重定向到你定义的 handler 中处理。原始对象本身完全不受影响,Proxy 拦截的是对代理对象的操作行为,不是修改原对象的属性访问机制。

常见误解是把它当“响应式封装工具”直接用,结果发现 Object.defineProperty 那套逻辑不适用——因为 Proxy 拦截的是运行时行为,不是静态属性定义。

必须掌握的 4 个基础 trap:get/set/has/ownKeys

这四个 trap 覆盖了日常 90% 的拦截需求。漏掉 hasownKeys,会导致 in 操作符、for...inObject.keys() 等行为不符合预期,尤其在做数据校验或权限控制时容易出错。

  • get(target, prop, receiver):拦截属性读取。注意必须返回值,否则为 undefined;若要保持原行为,需显式 return Reflect.get(...arguments)
  • set(target, prop, value, receiver):拦截赋值。必须返回布尔值(true 表示成功),否则会抛 TypeError
  • has(target, prop):拦截 prop in proxy。默认不触发,必须手动实现,否则始终返回 true(因为 Proxy 默认透传)
  • ownKeys(target):拦截 Object.getOwnPropertyNames()Reflect.ownKeys()。若目标对象有 Symbol 属性或不可枚举属性,这里必须显式返回,否则会被过滤掉
const target = { a: 1, b: 2 };
const handler = {
  get(target, prop) {
    console.log('get', prop);
    return prop in target ? target[prop] : 'default';
  },
  set(target, prop, value) {
    if (typeof value !== 'num

ber') { throw new TypeError('Only number allowed'); } target[prop] = value; return true; }, has(target, prop) { return prop !== 'secret' && prop in target; }, ownKeys(target) { return Object.keys(target); } }; const proxy = new Proxy(target, handler);

为什么不能直接代理数组?length 和索引更新会失效

Proxy 可以代理数组,但 pushpoplength 修改等操作不会自动触发 set,因为它们本质是调用原型方法,而数组的 length 是一个有 setter 的 accessor 属性——但 Proxy 默认不拦截原型链上的操作,除非你在 set 中额外判断 prop === 'length' 或拦截 defineProperty

更关键的是:proxy.push(1) 触发的是 proxy[proxy.length] = 1 + proxy.length++,后者会走 set,但前者若没实现 getlength 的响应,就可能读到旧值。

  • 代理数组时,务必在 get 中处理 length 的读取逻辑
  • 若需拦截 push/splice 等方法,得在 handler 中重写对应方法,或用 apply trap 拦截函数调用
  • 不要依赖 Array.isArray(proxy) 判断类型——它返回 true,但某些库(如 Vue 2 的响应式)内部靠 __proto__ 或构造器识别,Proxy 会破坏该链

性能与兼容性:别在热路径上无脑套 Proxy

每次属性访问都多一层函数调用,V8 虽已优化,但在循环体、渲染函数、高频事件回调中滥用 Proxy,会明显拖慢执行速度。尤其当 handler 里有复杂逻辑(如深克隆、正则匹配、网络请求)时,问题立刻暴露。

兼容性方面:Proxy 不支持 IE,且 Node.js Object.defineProperty + 递归代理(仅限已知属性)。

  • 生产环境建议只对明确需要拦截的顶层对象使用 Proxy,避免代理整个 store 或全局状态树
  • 开发阶段可用 console.trace() 在 trap 里打点,确认是否被意外高频触发
  • Chrome DevTools 的 “Blackbox script” 功能可隐藏 Proxy 内部代码,避免调试时跳进 handler
Proxy 的真正难点不在语法,而在想清楚「谁该被代理」「哪些操作必须拦截」「原语义要不要保留」——比如你拦了 set 却忘了同步更新 length,或者重写了 get 却没透传 Symbol.iterator,结果 [...proxy] 就崩了。