红宝书第二十讲:详解JavaScript的Proxy与Reflect

资料取自《JavaScript高级程序设计(第5版)》
查看总目录:红宝书学习大纲


一、Proxy的作用:给对象戴上“监听耳机”

Proxy(代理)允许你为对象创建代理层,拦截并自定义对象的基本操作(如属性读写、函数调用等)。核心用法:

  • 通过new Proxy(target, handler)创建代理对象
  • handler对象定义各种监听方法(称为陷阱/trap)12
示例1:拦截属性读取
// 创建目标对象
const target = { name: '小明', age: 18 };

// 定义代理处理器(拦截读取)
const handler = {
  get(target, property) {
    console.log(`有人在读取属性:${property}`);
    return Reflect.get(...arguments); // 利用Reflect执行默认操作
  }
};

// 创建代理对象
const proxy = new Proxy(target, handler);

console.log(proxy.name); // 输出:有人在读取属性 name → 小明

1: 资料1说明Proxy是目标对象的抽象层
2: 资料3展示使用Reflect执行默认行为


二、常见拦截操作(陷阱)

Handler对象可以定义多种陷阱,常用方法:

  • get() → 拦截属性读取
  • set() → 拦截属性设置
  • apply() → 拦截函数调用
  • has() → 拦截in操作符
示例2:拦截属性写入并进行验证
const validator = {
  set(target, prop, value) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new Error('年龄必须是数字!');
    }
    return Reflect.set(target, prop, value); // 通过Reflect正确设置值
  }
};

const proxy = new Proxy({}, validator);
proxy.age = 20;  // 正常
proxy.age = '20';// 抛出错误:年龄必须是数字!

3: 资料4说明陷阱如何配合Reflect使用


三、Reflect:Proxy的最佳搭档

Reflect API提供与Proxy陷阱同名的方法,用于执行默认操作24。优势:

  • 避免手动处理底层对象
  • 保持代码简洁性
示例3:简化代理的默认行为
// 完全透传的代理(所有操作自动反射到目标对象)
const target = { secret: 'test123' };
const proxy = new Proxy(target, Reflect); // 直接使用Reflect处理所有陷阱

console.log(proxy.secret); // test123(所有操作原样执行)

4: 资料6演示通过Reflect创建透传式代理


四、核心应用场景

  1. 数据验证(如示例2中的年龄验证)
  2. 日志记录(记录属性访问或修改)
  3. 动态扩展功能(自动生成属性或方法)
示例4:记录数组push操作
const array = [];
const handler = {
  get(target, prop) {
    if (prop === 'push') {
      return function(...args) {
        console.log(`添加了元素:${args}`);
        return Reflect.apply(target[prop], target, args); // 调用原始的push方法
      };
    }
    return Reflect.get(...arguments);
  }
};

const proxyArray = new Proxy(array, handler);
proxyArray.push(1, 2); // 输出:添加了元素:1,2 → 数组变为[1,2]

3: 资料4展示如何通过代理拦截数组方法


五、注意事项与规则

  • 必须遵守陷阱不变式(Trap Invariants)→ 例如.defineProperty()必须返回布尔值 5
  • 代理无法完全模拟目标对象(如某些内部属性无法拦截)1
示例5:陷阱不变式错误
const handler = {
  defineProperty() { // 未正确返回布尔值
    console.log('尝试定义属性');
    // 未调用Reflect.defineProperty
  }
};

const proxy = new Proxy({}, handler);
Object.defineProperty(proxy, 'foo', { value: 'bar' }); // 抛出TypeError

5: 资料5指出未正确使用Reflect方法可能导致错误


流程图:Proxy与Reflect协作流程

sequenceDiagram
    participant 用户
    participant Proxy
    participant Handler
    participant Reflect
    participant Target

    用户 ->>+ Proxy: 访问属性或方法
    Proxy ->>+ Handler: 调用对应陷阱(如get/set)
    Handler -->> Reflect: 通过Reflect执行默认操作
    Reflect -->> Target: 操作实际对象
    Target -->> Reflect: 返回结果
    Reflect -->> Handler: 处理结果
    Handler -->> Proxy: 返回最终值
    Proxy -->>- 用户: 获得结果

目录:总目录
上篇文章:红宝书第十九讲:详解JavaScript的Fetch API与Ajax请求

下篇文章:红宝书第二十一讲:详解JavaScript的模块化(CommonJS与ES Modules)

脚注


  1. 《JavaScript高级程序设计(第5版)》说明Proxy是目标对象的抽象层
  2. 《JavaScript高级程序设计(第5版)》展示Reflect方法简化默认操作
  3. 《JavaScript高级程序设计(第5版)》演示如何拦截数组的push操作
  4. 《JavaScript高级程序设计(第5版)》提供使用Reflect创建透传代理的示例
  5. 《JavaScript高级程序设计(第5版)》指出陷阱方法需遵守严格规则

kovli
10 声望4 粉丝