← 返回题目列表

Proxy 和 Reflect 是什么?它们通常怎么配合使用?

高频 中等 第 16 / 28 题 更新于 2026/07/28
ES6ProxyReflect

简化版

Proxy 用来拦截对象的读取、赋值、删除、函数调用等底层操作;Reflect 提供与这些操作对应的标准方法,常在拦截器里完成默认行为,避免手写对象操作带来的语义偏差。

详细版

Proxy 创建对象代理:

const proxy = new Proxy(target, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver);
  }
});

常见拦截点包括 getsethasdeletePropertyapplyconstructReflect 的方法名和代理陷阱基本对应,例如 Reflect.getReflect.set。 工程中它们常用于响应式系统、权限校验、数据校验、日志埋点和 API 包装。

面试重点:Proxy 是代理层,Reflect 是标准化默认操作;Vue 3 响应式就是高频应用场景。

完整版教学

一、Proxy 解决的是对象操作拦截问题

普通对象的属性读取和赋值默认是直接发生的。 如果希望在读取时收集依赖、赋值时触发更新、删除时校验权限,就需要一个统一拦截层。 Proxy 正是这个拦截层。

const target = { count: 1 };

const state = new Proxy(target, {
  get(obj, key) {
    console.log('read', key);
    return obj[key];
  },
  set(obj, key, value) {
    console.log('write', key, value);
    obj[key] = value;
    return true;
  }
});

state.count; // read count
state.count = 2; // write count 2

这个例子里 1 次读取和 1 次赋值都被代理捕获。 它说明 Proxy 不是复制对象,而是在目标对象外面包了一层操作入口。

二、Reflect 负责把默认行为标准化

在代理陷阱中直接写 target[key] 有时不够准确。 比如 getter 中的 this、原型链上的属性访问、赋值是否成功等语义,都需要遵守语言规范。 Reflect 提供了一组与底层操作对应的方法,适合在陷阱中调用默认行为。

const proxy = new Proxy(target, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    return Reflect.set(target, key, value, receiver);
  }
});
操作Proxy 陷阱Reflect 方法
读取属性getReflect.get
设置属性setReflect.set
删除属性deletePropertyReflect.deleteProperty
函数调用applyReflect.apply
new 调用constructReflect.construct

可以把 Reflect 看成“默认动作按钮”。 你先在 Proxy 里做额外逻辑,再交给 Reflect 完成原本该发生的行为。

三、receiver 参数是很多追问的关键

get(target, key, receiver) 中的 receiver 通常指最初触发属性访问的代理对象。 当属性是 getter,并且 getter 内部使用 this 时,receiver 会影响 this 指向。 如果忽略它,代理对象和原型链组合时可能出现错误行为。

const obj = {
  name: 'proxy',
  get label() {
    return this.name;
  }
};

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver);
  }
});

在简单对象上你可能看不出区别。 但响应式系统要处理嵌套对象、原型链和 getter,必须尽量保持语言默认语义。 这也是 Vue 3 源码里大量使用 Reflect 的原因之一。

四、Proxy 可以拦截的范围比 Object.defineProperty 更大

Object.defineProperty 主要劫持已有属性的 getter/setter。 Proxy 代理的是对象操作,可以感知新增属性、删除属性、in 判断、枚举等更多行为。 这使它更适合做完整响应式系统。

读取 obj.a
  -> get
新增 obj.b = 2
  -> set
删除 delete obj.a
  -> deleteProperty
'a' in obj
  -> has
维度Object.definePropertyProxy
新增属性需要额外处理可被 set 捕获
删除属性难直接捕获可被 deleteProperty 捕获
数组索引/长度处理复杂拦截更统一
兼容性更老浏览器可用旧环境不支持且难 polyfill

Proxy 的能力更强,但兼容性成本也更高。

五、Proxy 的限制也要讲清楚

Proxy 只能代理对象,不能直接代理原始值。 代理对象和目标对象不是同一个引用,身份比较会不同。 某些内建对象或私有字段也可能有特殊限制。

const target = {};
const proxy = new Proxy(target, {});

console.log(proxy === target); // false

如果一个系统里同时暴露 target 和 proxy,就可能出现状态绕过。 例如直接改 target 不经过代理层,响应式依赖就可能收不到通知。 工程里通常要避免把原始对象和代理对象混着传。

Proxy 是“入口控制”,不是“对象变身”;绕过入口直接碰 target,代理逻辑就不会执行。

六、典型应用:响应式、校验和权限控制

响应式系统会在 get 时收集依赖,在 set 时触发更新。 数据校验会在 set 时检查类型、范围和只读规则。 权限控制可以在 getdeleteProperty 中阻止访问敏感字段。

const user = new Proxy({ age: 18 }, {
  set(target, key, value, receiver) {
    if (key === 'age' && value < 0) {
      throw new Error('age must be positive');
    }
    return Reflect.set(target, key, value, receiver);
  }
});

如果 age = -1,代理会在赋值真正发生前拦截。 如果 age = 20,再交给 Reflect.set 完成标准赋值。 这就是“增强操作但保留默认语义”的典型模式。

七、常见误区与追问

  • 误区:Proxy 会复制一份 target。 Proxy 只是创建代理入口,内部仍关联原始 target,并不会自动深拷贝数据。
  • 误区:Reflect 只是 Object 方法的别名。 Reflect 更贴近语言内部操作,并与 Proxy 陷阱一一配合。
  • 误区:Proxy 能被完整 polyfill。 Proxy 拦截的是语言底层操作,旧环境很难用普通 JS 完整模拟。
  • 追问:为什么 set 陷阱要返回布尔值? 它表示赋值是否成功,严格模式下返回 false 可能触发错误。
  • 追问:Vue 3 为什么用 Proxy 做响应式? 它能统一拦截新增、删除、数组和集合等操作,覆盖范围比 defineProperty 更广。
  • 追问:receiver 有什么用? 它让 Reflect 在 getter 或原型链场景中保持正确的 this 和访问语义。

八、加强记忆

Proxy 记成“拦截入口”,Reflect 记成“默认动作”。回答时按三个层次展开:先说 Proxy 能拦哪些操作,再说 Reflect 为什么常用于陷阱里,最后补工程场景和限制,尤其是响应式、兼容性、target 与 proxy 身份不同。