17370845950

什么是JavaScript代理与反射的高级用法_元编程实战技巧详解【教程】
Proxy 和 Reflect 是 JavaScript 元编程的底层基础设施,必须配合使用以保障属性描述符、receiver 绑定和原型链查找等关键语义正确。

ProxyReflect 不是语法糖,也不是“高级玩具”——它们是 JavaScript 元编程的底层基础设施。你写一个响应式系统、做字段级权限控制、拦截 API 调用、甚至实现运行时类型校验,绕不开它们。关键不是“会不会用”,而是“为什么必须这么用”。

为什么 set/get 拦截里非得用 Reflect.setReflect.get

直接写 target[prop] = valuetarget[prop] 看似省事,但会悄悄破坏三类关键语义:

  • 绕过属性描述符:比如 writable: false 的属性,硬赋值不会报错(非严格模式下静默失败),而 Reflect.set() 会按规范返回 false,让你能拦截并抛错
  • 丢失 receiver:访问器(get name() { return this._name; })里的 this 必须指向代理对象本身,否则读不到代理上定义的私有字段;Reflect.get(target, prop, receiver) 是唯一能正确传递 receiver 的方式
  • 跳过原型链查找:target[prop] 只查自身属性,Reflect.get() 才会走完整的 [[Get]] 内部流程,包括 __proto__ 链和 Symbol 属性
const obj = {
  get name() { return this._name || 'Anonymous'; },
  set name(v) { this._name = v.toUpperCase(); }
};
const proxy = new Proxy(obj, {
  get(target, prop, receiver) {
    console.log(`GET ${prop}`);
    return Reflect.get(target, prop, receiver); // ✅ 正确
    // return target[prop]; // ❌ this 指向 target,_name 读写失效
  }
});
console.log(proxy.name); // "Anonymous"

怎么实现真正可靠的只读代理?

只拦 set 是常见误区。用户仍可通过 deletePropertydefinePropertypreventExtensions 等方式篡改对象结构。

  • 必须拦截全部可变操作:setdeletePropertydefinePropertypreventExtensionssetPrototypeOf
  • 类型校验逻辑放在 set 里,但赋值动作必须交由 Reflect.set() 完成,否则会丢掉 enumerable/configurable 等描述符信息
  • get 中仍要用 Reflect.get(),否则嵌套对象、Symbol 属性、继承来的访问器全失效
function readOnly(obj) {
  return new Proxy(obj, {
    set() { throw new Error('Cannot assign to read-only object'); },
    deleteProperty() { throw new Error('Cannot delete from read-only object'); },
    defineProperty() { throw new Error('Cannot define property on read-only object'); },
    preventExtensions() { return true; },
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver);
      return typeof result === 'object' && result !== null
        ? readOnly(result)
        : result;
    }
  });
}

如何用 Proxy + Reflect 做轻量响应式(类似 Vue 3 原理)?

核心不在“劫持”,而在“触发时机可控”和“行为不越界”。Reflect 让你能安全转发默认行为,再插手副作用;Proxy 提供入口点。

  • get 拦截中调用 Reflect.get() 后,立即执行依赖收集(比如把当前 effect 存进 Dep
  • set
    拦截中先用 Reflect.set() 完成赋值,再触发更新(比如遍历 Dep 里存的函数并执行)
  • 对嵌套对象递归代理时,Reflect.get() 返回值需判断是否为对象,避免无限代理 null 或循环引用
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      track(target, key); // 收集依赖
      return typeof res === 'object' && res !== null
        ? reactive(res)
        : res;
    },
    set(target, key, value, receiver) {
      const oldVal = target[key];
      const res = Reflect.set(target, key, value, receiver);
      if (oldVal !== value) trigger(target, key); // 触发更新
      return res;
    }
  });
}
最常被忽略的一点:所有 Reflect 方法都要求参数顺序和语义严格匹配引擎内部操作——漏传 receiver、错用 Reflect.has() 替代 in、或在 construct trap 里不用 Reflect.construct(),都会导致继承链断裂、this 绑定错误、或构造失败静默。这不是“推荐用”,而是“必须用”。