Skip to content

深入理解 Proxy 与 Reflect

13个拦截器完整用法、Vue3响应式原理、可撤销代理与不变量约束

什么是 Proxy 与 Reflect?

定义Proxy 是 ES6 引入的元编程机制,允许你创建一个对象的"代理",通过 13 种拦截器(trap)自定义该对象的基本操作(读取、赋值、删除、枚举等)。Reflect 是与 Proxy 配套的内置对象,提供了与 13 种拦截器一一对应的静态方法,用于执行对象的默认行为。

涉及场景

  • Vue3 响应式系统reactive() 底层使用 Proxy 拦截 get/set 实现依赖收集和派发更新
  • 数据验证:拦截 set 操作,在赋值前校验数据类型和范围
  • 访问控制:拦截 get 操作,实现属性的只读、权限控制
  • 日志追踪:自动记录对象的读写操作,用于调试和审计
  • 默认值处理:访问不存在的属性时返回默认值而非 undefined
  • 不可变数据:创建深度冻结的对象,任何修改都抛出错误

作用

  1. 替代 Object.defineProperty:无需预先知道属性名,可拦截新增属性、数组操作等
  2. 元编程能力:从语言层面自定义对象的底层行为
  3. AOP 编程:在不修改原对象的前提下,添加横切关注点(日志、缓存、验证)
  4. 框架核心:现代前端框架响应式系统的基石
javascript
const target = { name: '张三', age: 25 };

const proxy = new Proxy(target, {
  get(target, prop, receiver) {
    console.log(`读取了 ${String(prop)}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置 ${String(prop)} = ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
});

proxy.name;       // 读取了 name → '张三'
proxy.age = 26;   // 设置 age = 26

什么是 Reflect?

Reflect 是一个内置对象,提供了与 Proxy handler 一一对应的静态方法。它的设计目的:

  1. 将 Object 的命令式操作变为函数式Reflect.has(obj, key) 替代 key in obj
  2. 提供默认行为 — 在 Proxy handler 中调用 Reflect 执行默认操作
  3. 返回布尔值替代抛异常Reflect.defineProperty() 返回 true/false,而不是抛错
javascript
// Object 操作 → Reflect 等价
'name' in obj            → Reflect.has(obj, 'name')
delete obj.name          → Reflect.deleteProperty(obj, 'name')
Object.keys(obj)         → Reflect.ownKeys(obj)  // 注意:包含Symbol
obj.name                 → Reflect.get(obj, 'name')
obj.name = 'value'       → Reflect.set(obj, 'name', 'value')
new Constructor(...args) → Reflect.construct(Constructor, args)
fn.apply(ctx, args)      → Reflect.apply(fn, ctx, args)

13个拦截器(Trap)完整列表

1. get — 读取属性

javascript
const handler = {
  // target: 目标对象
  // prop: 属性名(string 或 Symbol)
  // receiver: proxy 本身或继承 proxy 的对象
  get(target, prop, receiver) {
    if (prop in target) {
      return Reflect.get(target, prop, receiver);
    }
    throw new ReferenceError(`属性 "${String(prop)}" 不存在`);
  }
};

const obj = new Proxy({ name: '张三' }, handler);
obj.name; // '张三'
// obj.xxx; // ❌ ReferenceError: 属性 "xxx" 不存在

2. set — 设置属性

javascript
const handler = {
  // value: 设置的值
  // 返回 true 表示成功,false 会在严格模式下抛 TypeError
  set(target, prop, value, receiver) {
    if (prop === 'age' && (typeof value !== 'number' || value < 0)) {
      throw new TypeError('age 必须是非负数');
    }
    return Reflect.set(target, prop, value, receiver);
  }
};

const user = new Proxy({}, handler);
user.name = '张三'; // ✅
user.age = 25;      // ✅
// user.age = -1;   // ❌ TypeError: age 必须是非负数
// user.age = 'old'; // ❌ TypeError

3. has — in 操作符

javascript
const handler = {
  has(target, prop) {
    // 隐藏以 _ 开头的私有属性
    if (typeof prop === 'string' && prop.startsWith('_')) {
      return false;
    }
    return Reflect.has(target, prop);
  }
};

const obj = new Proxy({ _secret: 42, name: '张三' }, handler);
console.log('name' in obj);    // true
console.log('_secret' in obj); // false(被隐藏了)

4. deleteProperty — delete 操作符

javascript
const handler = {
  deleteProperty(target, prop) {
    if (typeof prop === 'string' && prop.startsWith('_')) {
      throw new Error(`不能删除私有属性 ${prop}`);
    }
    return Reflect.deleteProperty(target, prop);
  }
};

const obj = new Proxy({ _id: 1, name: '张三' }, handler);
delete obj.name;  // ✅
// delete obj._id; // ❌ Error: 不能删除私有属性 _id

5. ownKeys — 枚举属性

javascript
const handler = {
  // 拦截: Object.keys(), Object.getOwnPropertyNames(),
  //       Object.getOwnPropertySymbols(), Reflect.ownKeys()
  ownKeys(target) {
    // 过滤掉 _ 开头的属性
    return Reflect.ownKeys(target).filter(
      key => typeof key !== 'string' || !key.startsWith('_')
    );
  }
};

const obj = new Proxy({ _id: 1, name: '张三', age: 25 }, handler);
console.log(Object.keys(obj)); // ['name', 'age'](_id 被隐藏)

6. getOwnPropertyDescriptor — 属性描述符

javascript
const handler = {
  getOwnPropertyDescriptor(target, prop) {
    // 隐藏私有属性的描述符
    if (typeof prop === 'string' && prop.startsWith('_')) {
      return undefined;
    }
    return Reflect.getOwnPropertyDescriptor(target, prop);
  }
};

7. defineProperty — 定义属性

javascript
const handler = {
  defineProperty(target, prop, descriptor) {
    // 阻止定义不可枚举的属性
    if (descriptor.enumerable === false) {
      throw new Error('所有属性必须是可枚举的');
    }
    return Reflect.defineProperty(target, prop, descriptor);
  }
};

8. getPrototypeOf — 获取原型

javascript
const handler = {
  getPrototypeOf(target) {
    // 拦截: Object.getPrototypeOf(), __proto__, instanceof
    return Reflect.getPrototypeOf(target);
  }
};

9. setPrototypeOf — 设置原型

javascript
const handler = {
  setPrototypeOf(target, proto) {
    // 禁止修改原型
    throw new Error('不允许修改原型');
  }
};

const obj = new Proxy({}, handler);
// Object.setPrototypeOf(obj, {}); // ❌ Error

10. isExtensible — 是否可扩展

javascript
const handler = {
  isExtensible(target) {
    return Reflect.isExtensible(target);
  }
};

11. preventExtensions — 阻止扩展

javascript
const handler = {
  preventExtensions(target) {
    return Reflect.preventExtensions(target);
  }
};

12. apply — 函数调用(仅函数代理)

javascript
const handler = {
  // target: 目标函数
  // thisArg: this
  // args: 参数列表
  apply(target, thisArg, args) {
    console.log(`调用 ${target.name}(${args.join(', ')})`);
    const start = performance.now();
    const result = Reflect.apply(target, thisArg, args);
    console.log(`耗时: ${(performance.now() - start).toFixed(2)}ms`);
    return result;
  }
};

function sum(a, b) { return a + b; }
const proxySum = new Proxy(sum, handler);
proxySum(1, 2);
// 调用 sum(1, 2)
// 耗时: 0.01ms
// → 3

13. construct — new 操作(仅函数代理)

javascript
const handler = {
  // target: 目标构造函数
  // args: 参数列表
  // newTarget: 被调用的构造函数(通常等于 proxy)
  construct(target, args, newTarget) {
    console.log(`new ${target.name}(${args.join(', ')})`);
    return Reflect.construct(target, args, newTarget);
  }
};

function Person(name) { this.name = name; }
const ProxyPerson = new Proxy(Person, handler);
const p = new ProxyPerson('张三');
// new Person(张三)

实战应用

1. Vue3 响应式原理(简化版)

javascript
// Vue3 的 reactive() 核心实现
const targetMap = new WeakMap();
let activeEffect = null;

function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key);
      const result = Reflect.get(target, key, receiver);
      // 深层代理
      if (typeof result === 'object' && result !== null) {
        return reactive(result);
      }
      return result;
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        // 触发更新
        trigger(target, key);
      }
      return result;
    },
    deleteProperty(target, key) {
      const hadKey = key in target;
      const result = Reflect.deleteProperty(target, key);
      if (hadKey && result) {
        trigger(target, key);
      }
      return result;
    }
  });
}

function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));
  }
  let dep = depsMap.get(key);
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  dep.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(effect => effect());
  }
}

function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

// 使用
const state = reactive({ count: 0, name: '张三' });

effect(() => {
  console.log(`count = ${state.count}`);
});
// 立即输出: count = 0

state.count++; // 自动输出: count = 1
state.count++; // 自动输出: count = 2

2. 数据验证器

javascript
function createValidator(schema) {
  return new Proxy({}, {
    set(target, prop, value) {
      const rule = schema[prop];
      if (!rule) {
        throw new Error(`未知属性: ${prop}`);
      }

      // 类型检查
      if (rule.type && typeof value !== rule.type) {
        throw new TypeError(`${prop} 必须是 ${rule.type},收到 ${typeof value}`);
      }

      // 范围检查
      if (rule.min !== undefined && value < rule.min) {
        throw new RangeError(`${prop} 最小值为 ${rule.min}`);
      }
      if (rule.max !== undefined && value > rule.max) {
        throw new RangeError(`${prop} 最大值为 ${rule.max}`);
      }

      // 正则检查
      if (rule.pattern && !rule.pattern.test(value)) {
        throw new Error(`${prop} 格式不正确`);
      }

      // 自定义验证
      if (rule.validate && !rule.validate(value)) {
        throw new Error(`${prop} 验证失败: ${rule.message || '无效值'}`);
      }

      return Reflect.set(target, prop, value);
    }
  });
}

const user = createValidator({
  name: { type: 'string', pattern: /^.{2,20}$/ },
  age: { type: 'number', min: 0, max: 150 },
  email: { type: 'string', pattern: /^\S+@\S+\.\S+$/ }
});

user.name = '张三';              // ✅
user.age = 25;                   // ✅
user.email = 'test@example.com'; // ✅
// user.age = -1;                // ❌ RangeError
// user.email = 'invalid';       // ❌ Error: email 格式不正确

3. 负索引数组

javascript
function createNegativeArray(arr) {
  return new Proxy(arr, {
    get(target, prop, receiver) {
      const index = Number(prop);
      if (Number.isInteger(index) && index < 0) {
        // 负索引:-1 表示最后一个元素
        return Reflect.get(target, target.length + index, receiver);
      }
      return Reflect.get(target, prop, receiver);
    }
  });
}

const arr = createNegativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
console.log(arr[0]);  // 1

4. 属性访问链安全(可选链替代方案)

javascript
function safe(obj) {
  return new Proxy(obj, {
    get(target, prop) {
      const value = Reflect.get(target, prop);
      if (value === undefined || value === null) {
        return safe({}); // 返回空代理,避免 TypeError
      }
      if (typeof value === 'object') {
        return safe(value);
      }
      return value;
    }
  });
}

const data = { a: { b: { c: 42 } } };
console.log(safe(data).a.b.c);       // 42
console.log(safe(data).x.y.z);       // Proxy{}(不会报错)
// 现在推荐用可选链 data?.a?.b?.c,但这展示了 Proxy 的能力

5. 不可变对象(深冻结)

javascript
function deepFreeze(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      const value = Reflect.get(target, prop, receiver);
      if (typeof value === 'object' && value !== null) {
        return deepFreeze(value); // 递归代理
      }
      return value;
    },
    set() {
      throw new Error('对象不可修改');
    },
    deleteProperty() {
      throw new Error('对象不可删除属性');
    },
    defineProperty() {
      throw new Error('对象不可定义新属性');
    }
  });
}

const frozen = deepFreeze({ a: { b: { c: 1 } } });
console.log(frozen.a.b.c); // 1
// frozen.a.b.c = 2;       // ❌ Error: 对象不可修改

可撤销代理(Proxy.revocable)

javascript
const { proxy, revoke } = Proxy.revocable({ name: '张三' }, {
  get(target, prop) {
    return Reflect.get(target, prop);
  }
});

console.log(proxy.name); // '张三'

revoke(); // 撤销代理

// proxy.name; // ❌ TypeError: Cannot perform 'get' on a proxy that has been revoked
// proxy.name = '李四'; // ❌ TypeError

// 适用场景:
// 1. 临时授权:给第三方库临时访问权限,用完后撤销
// 2. 资源管理:确保代理不会在不需要时被继续使用
// 3. 安全隔离:API token 过期后撤销代理

Proxy 不变量(Invariant)

Proxy 的 handler 不能违反某些规则,否则会抛出 TypeError:

javascript
// 1. get:不可配置且不可写的属性,代理必须返回原始值
const obj = {};
Object.defineProperty(obj, 'x', {
  value: 42,
  writable: false,
  configurable: false
});

const proxy = new Proxy(obj, {
  get() { return 100; } // ❌ 试图返回不同值
});
// proxy.x; // TypeError: 'get' on proxy: property 'x' is a read-only
//          // and non-configurable data property on the proxy target
//          // but the proxy did not return its actual value

// 2. set:不可配置且不可写的属性不能被修改
// 3. has:不可配置的属性不能被隐藏
// 4. deleteProperty:不可配置的属性不能被删除
// 5. ownKeys:必须包含目标对象所有不可配置的自有属性
// 6. getPrototypeOf:如果目标不可扩展,必须返回实际原型

为什么用 Reflect 而不直接操作 target?

javascript
// ❌ 直接操作可能出问题
const proxy = new Proxy(obj, {
  get(target, prop) {
    return target[prop]; // 可能绕过其他代理层
  },
  set(target, prop, value) {
    target[prop] = value; // 不返回布尔值
    return true;
  }
});

// ✅ 使用 Reflect
const proxy2 = new Proxy(obj, {
  get(target, prop, receiver) {
    return Reflect.get(target, prop, receiver);
    // 1. 正确传递 receiver(处理 getter 中的 this)
    // 2. 返回值语义正确
  },
  set(target, prop, value, receiver) {
    return Reflect.set(target, prop, value, receiver);
    // 1. 正确传递 receiver(处理 setter 中的 this)
    // 2. 自动返回布尔值
  }
});

// receiver 的重要性
const parent = {
  get value() {
    return this._value; // this 应该指向谁?
  }
};

const child = Object.create(
  new Proxy(parent, {
    get(target, prop, receiver) {
      console.log('receiver is child?', receiver === child);
      // 使用 Reflect.get 时 receiver 正确传递
      return Reflect.get(target, prop, receiver);
    }
  })
);

child._value = 42;
child.value; // receiver is child? true → 42
// 如果用 target[prop],this 会指向 parent,而不是 child

Proxy vs Object.defineProperty

javascript
// Vue2 用 Object.defineProperty,Vue3 用 Proxy

// Object.defineProperty 的局限:
const obj = { a: 1 };

// 1. 必须遍历每个属性
Object.keys(obj).forEach(key => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get() { return value; },
    set(newVal) { value = newVal; }
  });
});

// 2. ❌ 无法监听新增属性
obj.b = 2; // 不会触发 setter(Vue2 的 $set 就是为了解决这个问题)

// 3. ❌ 无法监听数组索引变化
const arr = [1, 2, 3];
arr[0] = 10; // 不会触发(Vue2 重写了数组方法)

// 4. ❌ 无法监听 delete
delete obj.a; // 不会触发

// Proxy 全部支持:
const proxyObj = new Proxy({}, {
  get(t, p, r) { return Reflect.get(t, p, r); },
  set(t, p, v, r) {
    console.log('set', p, v); // ✅ 新增属性也能监听
    return Reflect.set(t, p, v, r);
  },
  deleteProperty(t, p) {
    console.log('delete', p); // ✅ 删除也能监听
    return Reflect.deleteProperty(t, p);
  }
});

proxyObj.newProp = 'hello'; // ✅ set newProp hello
delete proxyObj.newProp;     // ✅ delete newProp
特性Object.definePropertyProxy
监听新增属性
监听删除属性
监听数组索引
深层监听需递归惰性递归(性能更好)
兼容性IE9+IE 完全不支持
性能初始化开销大访问时拦截

Reflect 全部 API

javascript
Reflect.get(target, prop, receiver)           // 读取属性
Reflect.set(target, prop, value, receiver)    // 设置属性
Reflect.has(target, prop)                     // in 操作符
Reflect.deleteProperty(target, prop)          // delete 操作符
Reflect.ownKeys(target)                       // 所有自有属性键
Reflect.getOwnPropertyDescriptor(target, prop) // 属性描述符
Reflect.defineProperty(target, prop, desc)    // 定义属性
Reflect.getPrototypeOf(target)                // 获取原型
Reflect.setPrototypeOf(target, proto)         // 设置原型
Reflect.isExtensible(target)                  // 是否可扩展
Reflect.preventExtensions(target)             // 阻止扩展
Reflect.apply(fn, thisArg, args)              // 调用函数
Reflect.construct(Target, args, newTarget)    // new 操作

总结

Proxy & Reflect 核心要点:
┌──────────────────────────────────────────────────────────┐
│ Proxy                                                     │
│ • 13个 trap 拦截对象的所有基本操作                           │
│ • 不修改原对象,创建代理层                                   │
│ • 支持数组、函数、Map/Set 等所有对象类型                      │
│ • Proxy.revocable() 创建可撤销代理                         │
│ • 有不变量约束,不能违反 JS 引擎的规则                        │
├──────────────────────────────────────────────────────────┤
│ Reflect                                                   │
│ • 与 Proxy trap 一一对应的静态方法                          │
│ • 提供默认行为,在 handler 中调用                            │
│ • 正确传递 receiver(处理 getter/setter 中的 this)         │
│ • 返回布尔值而非抛异常(比 Object 方法更安全)                │
├──────────────────────────────────────────────────────────┤
│ 实战应用                                                   │
│ • Vue3 响应式系统(reactive/ref/computed)                  │
│ • 数据验证 / 类型检查                                       │
│ • 访问控制 / 权限管理                                       │
│ • 日志记录 / 性能监控                                       │
│ • 不可变数据结构                                            │
│ • 负索引数组 / 默认值 / 缓存                                │
└──────────────────────────────────────────────────────────┘