JavaScript 代理(Proxy)与反射(Reflect)详解

在现代 JavaScript 开发中,代理(Proxy)反射(Reflect) 是两个非常强大的特性。

它们可以帮助我们 拦截对象操作、控制属性访问、实现验证逻辑,甚至实现响应式系统

下面我们按照知识点逐一展开。


1. 创建空代理

最基本的代理写法如下:

ini 复制代码
const target = { name: "Alice" };
const proxy = new Proxy(target, {}); // 空代理,没有拦截行为

proxy.age = 20;

console.log(proxy.name);  // "Alice"
console.log(target.age);  // 20

👉 空代理只是 target 的一个"镜像",没有任何特殊功能。


2. 定义捕获器(Traps)

捕获器就是代理的"拦截器"。例如 get 捕获属性访问:

javascript 复制代码
const target = { name: "Alice" };

const proxy = new Proxy(target, {
  get(obj, prop) {
    console.log(`访问属性: ${prop}`);
    return obj[prop];
  }
});

console.log(proxy.name); // 输出: 访问属性: name \n "Alice"

3. 捕获器与参数

大多数捕获器会接收 target、prop、receiver 参数。例如:

javascript 复制代码
const target = { age: 25 };

const proxy = new Proxy(target, {
  get(t, prop, receiver) {
    console.log(`读取属性: ${prop}`);
    return Reflect.get(t, prop, receiver); // 推荐用 Reflect 保持一致性
  }
});

console.log(proxy.age); // 输出: 读取属性: age \n 25

4. 捕获器不变式

代理必须遵循 JavaScript 对象的不变式(invariants),否则会抛错。

javascript 复制代码
// 示例一
const obj = Object.freeze({ x: 10 });

const proxy = new Proxy(obj, {
  get() {
    return 42; // ❌ 不能篡改冻结对象的不变式
  }
});

console.log(proxy.x); // 10,而不是 42


// 示例二
const obj = {};
Object.defineProperty(obj, "y", {
  value: 100,
  writable: false,      // ❌ 不可写
  configurable: false   // ❌ 不可重新定义
});

const proxy = new Proxy(obj, {
  get() {
    return 200; // ❌ 尝试违反不变式
  }
});

console.log(proxy.y); 
// 输出: 100
// 说明:代理必须保持与目标对象一致,
// 不允许修改不可写 + 不可配置属性的值

5. 可撤销代理

代理可以通过 Proxy.revocable 创建,并支持 撤销

javascript 复制代码
const { proxy, revoke } = Proxy.revocable({ msg: "Hello" }, {
  get(t, prop) {
    return t[prop];
  }
});

console.log(proxy.msg); // "Hello"
revoke(); // 撤销代理
// console.log(proxy.msg); // ❌ 报错:代理已撤销

6. 实用反射 API

Reflect 提供了一套方法,和代理捕获器一一对应。

javascript 复制代码
const obj = { x: 10 };

console.log(Reflect.get(obj, "x")); // 10
Reflect.set(obj, "y", 20);
console.log(obj.y); // 20

👉 使用 Reflect 操作对象,比直接操作更安全规范。


7. 代理另一个代理

代理本身也能被再次代理:

javascript 复制代码
const target = { value: 1 };

const proxy1 = new Proxy(target, {
  get(t, prop) {
    console.log("proxy1 get");
    return Reflect.get(t, prop);
  }
});

const proxy2 = new Proxy(proxy1, {
  get(t, prop) {
    console.log("proxy2 get");
    return Reflect.get(t, prop);
  }
});

console.log(proxy2.value);
// 输出:proxy2 get \n proxy1 get \n 1

8. 代理的问题与不足

  • 性能开销:拦截操作有额外消耗。
  • 调试困难:过度使用会让逻辑难以理解。
  • 部分内建对象(如 DOM 节点)可能无法完全代理。

9. 代理捕获器与反射方法

每个捕获器都有对应的 Reflect 方法,推荐配合使用。下面给出示例:

9.1 get ↔ Reflect.get

javascript 复制代码
const obj = { name: "Alice" };
const proxy = new Proxy(obj, {
  get(t, prop, receiver) {
    console.log(`读取 ${prop}`);
    return Reflect.get(t, prop, receiver);
  }
});
console.log(proxy.name); // 输出: 读取 name \n Alice

9.2 set ↔ Reflect.set

javascript 复制代码
const obj = {};
const proxy = new Proxy(obj, {
  set(t, prop, value, receiver) {
    console.log(`设置 ${prop} = ${value}`);
    return Reflect.set(t, prop, value, receiver);
  }
});
proxy.age = 30; // 输出: 设置 age = 30

9.3 has ↔ Reflect.has

javascript 复制代码
const obj = { x: 10 };
const proxy = new Proxy(obj, {
  has(t, prop) {
    console.log(`检查是否有 ${prop}`);
    return Reflect.has(t, prop);
  }
});
console.log("x" in proxy); // 输出: 检查是否有 x \n true

9.4 defineProperty ↔ Reflect.defineProperty

javascript 复制代码
const obj = {};
const proxy = new Proxy(obj, {
  defineProperty(t, prop, desc) {
    console.log(`定义属性 ${prop}`);
    return Reflect.defineProperty(t, prop, desc);
  }
});
Object.defineProperty(proxy, "name", { value: "Bob" });
console.log(obj.name); // Bob

9.5 getOwnPropertyDescriptor ↔ Reflect.getOwnPropertyDescriptor

javascript 复制代码
const obj = { a: 1 };
const proxy = new Proxy(obj, {
  getOwnPropertyDescriptor(t, prop) {
    console.log(`获取属性描述符 ${prop}`);
    return Reflect.getOwnPropertyDescriptor(t, prop);
  }
});
console.log(Object.getOwnPropertyDescriptor(proxy, "a"));

9.6 deleteProperty ↔ Reflect.deleteProperty

javascript 复制代码
const obj = { secret: "123" };
const proxy = new Proxy(obj, {
  deleteProperty(t, prop) {
    console.log(`删除属性 ${prop}`);
    return Reflect.deleteProperty(t, prop);
  }
});
delete proxy.secret; // 输出: 删除属性 secret

9.7 ownKeys ↔ Reflect.ownKeys

javascript 复制代码
const obj = { x: 1, y: 2 };
const proxy = new Proxy(obj, {
  ownKeys(t) {
    console.log("获取所有键");
    return Reflect.ownKeys(t);
  }
});
console.log(Object.keys(proxy)); // 输出: 获取所有键 \n ["x","y"]

9.8 getPrototypeOf ↔ Reflect.getPrototypeOf

javascript 复制代码
const obj = {};
const proxy = new Proxy(obj, {
  getPrototypeOf(t) {
    console.log("获取原型");
    return Reflect.getPrototypeOf(t);
  }
});
console.log(Object.getPrototypeOf(proxy));

9.9 setPrototypeOf ↔ Reflect.setPrototypeOf

javascript 复制代码
const obj = {};
const proto = { greet: () => "hi" };
const proxy = new Proxy(obj, {
  setPrototypeOf(t, proto) {
    console.log("设置原型");
    return Reflect.setPrototypeOf(t, proto);
  }
});
Object.setPrototypeOf(proxy, proto); // 输出: 设置原型
console.log(obj.greet()); // hi

9.10 isExtensible ↔ Reflect.isExtensible

javascript 复制代码
const obj = {};
const proxy = new Proxy(obj, {
  isExtensible(t) {
    console.log("检查是否可扩展");
    return Reflect.isExtensible(t);
  }
});
console.log(Object.isExtensible(proxy)); // true

9.11 preventExtensions ↔ Reflect.preventExtensions

javascript 复制代码
const obj = {};
const proxy = new Proxy(obj, {
  preventExtensions(t) {
    console.log("禁止扩展");
    return Reflect.preventExtensions(t);
  }
});
Object.preventExtensions(proxy); // 输出: 禁止扩展

9.12 apply ↔ Reflect.apply

javascript 复制代码
function sum(a, b) { return a + b; }
const proxy = new Proxy(sum, {
  apply(fn, thisArg, args) {
    console.log("调用函数:", args);
    return Reflect.apply(fn, thisArg, args);
  }
});
console.log(proxy(2, 3)); // 输出: 调用函数: [2,3] \n 5

9.13 construct ↔ Reflect.construct

javascript 复制代码
function Person(name) { this.name = name; }
const proxy = new Proxy(Person, {
  construct(t, args, newTarget) {
    console.log("构造函数调用:", args);
    return Reflect.construct(t, args, newTarget);
  }
});
const p = new proxy("Alice"); 
// 输出: 构造函数调用: [ 'Alice' ]
console.log(p.name); // Alice

10. 跟踪属性访问

javascript 复制代码
function track(obj) {
  return new Proxy(obj, {
    get(t, prop) {
      console.log(`访问 ${prop}`);
      return Reflect.get(t, prop);
    }
  });
}
const user = track({ name: "Tom", age: 20 });
console.log(user.name);

11. 隐藏属性

javascript 复制代码
function hide(obj, keys) {
  return new Proxy(obj, {
    get(t, prop) {
      if (keys.includes(prop)) return undefined;
      return Reflect.get(t, prop);
    },
    ownKeys(t) {
      return Reflect.ownKeys(t).filter(k => !keys.includes(k));
    }
  });
}
const user = hide({ name: "Alice", password: "123" }, ["password"]);
console.log(user.password); // undefined
console.log(Object.keys(user)); // ["name"]

12. 属性验证

javascript 复制代码
const person = new Proxy({}, {
  set(t, prop, value) {
    if (prop === "age" && typeof value !== "number") {
      throw new TypeError("年龄必须是数字");
    }
    return Reflect.set(t, prop, value);
  }
});
person.age = 30;   // ✅
person.age = "20"; // ❌ 抛错

13. 函数与构造函数参数验证

javascript 复制代码
function sum(a, b) { return a + b; }

const safeSum = new Proxy(sum, {
  apply(fn, thisArg, args) {
    if (!args.every(n => typeof n === "number")) {
      throw new TypeError("参数必须是数字");
    }
    return Reflect.apply(fn, thisArg, args);
  }
});

console.log(safeSum(2, 3)); // 5
// console.log(safeSum(2, "x")); // 抛错

14. 数据绑定与可观察对象

javascript 复制代码
function observable(obj, callback) {
  return new Proxy(obj, {
    set(t, prop, value) {
      const result = Reflect.set(t, prop, value);
      callback(prop, value);
      return result;
    }
  });
}
const state = observable({ count: 0 }, (k, v) => {
  console.log(`${k} 更新为 ${v}`);
});
state.count++; // 输出: count 更新为 1

总结

  • Proxy 让我们可以拦截并自定义对象操作。
  • Reflect 提供与代理捕获器对应的标准 API,确保操作符合规范。
  • 常见用途包括:属性跟踪、隐藏敏感信息、数据验证、响应式编程。
相关推荐
小离a_a35 分钟前
使用原生css实现word目录样式,标题后面的...动态长度并始终在标题后方(生成点线)
前端·css
郭优秀的笔记1 小时前
抽奖程序web程序
前端·css·css3
布兰妮甜1 小时前
CSS Houdini 与 React 19 调度器:打造极致流畅的网页体验
前端·css·react.js·houdini
小小愿望2 小时前
ECharts 实战技巧:揭秘 X 轴末项标签 “莫名加粗” 之谜及破解之道
前端·echarts
小小愿望2 小时前
移动端浏览器中设置 100vh 却出现滚动条?
前端·javascript·css
fail_to_code2 小时前
请不要再只会回答宏任务和微任务了
前端
摸着石头过河的石头2 小时前
taro3.x-4.x路由拦截如何破?
前端·taro
lpfasd1232 小时前
开发Chrome/Edge插件基本流程
前端·chrome·edge
练习前端两年半3 小时前
🚀 Vue3 源码深度解析:Diff算法的五步优化策略与最长递增子序列的巧妙应用
前端·vue.js
烛阴3 小时前
TypeScript 接口入门:定义代码的契约与形态
前端·javascript·typescript