当前位置: 首页 > news >正文

Proxy与Reflect

ES6中的Proxy与Reflect

JavaScript的Proxy(代理)和Reflect(反射)是ES6引入的特性,它们共同为对象操作提供了便捷。

一、Proxy:对象操作的拦截器

Proxy本质上是一个"包装器",它可以包裹任何对象(包括数组、函数甚至另一个Proxy),并在原对象的操作执行前插入自定义逻辑。红宝书中将proxy类比c++中的指针(只是帮助理解,实际存在很大区别),可以用作目标对象的替身,但又完全独立于目标对象。目标对象既可以直接被操作,也可以通过代理来操作,但直接操作会绕过代理施予的行为。

1. Proxy的基本使用

在代理对象上执行的任何操作实际上都会应用到目标对象

const target = { id: 'target' 
}; 
const handler = {}; 
const proxy = new Proxy(target, handler); 
// id 属性会访问同一个值
console.log(target.id); // target 
console.log(proxy.id); // target 
// 给目标属性赋值会反映在两个对象上
// 因为两个对象访问的是同一个值
target.id = 'foo'; 
console.log(target.id); // foo 
console.log(proxy.id); // foo 
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar'; 
console.log(target.id); // bar 
console.log(proxy.id); // bar 
// hasOwnProperty()方法在两个地方
// 都会应用到目标对象
console.log(target.hasOwnProperty('id')); // true 
console.log(proxy.hasOwnProperty('id')); // true 
// Proxy.prototype 是 undefined 
// 因此不能使用 instanceof 操作符
console.log(target instanceof Proxy); // TypeError
console.log(proxy instanceof Proxy); // TypeError// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false

2. Proxy的捕获器使用

使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的
拦截器”。每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接
或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对
象之前先调用捕获器函数,从而拦截并修改相应的行为。

const target = { name: "张三" };
const handler = {// 拦截器方法get(target, propKey, receiver) {console.log(`读取属性: ${propKey}`);return target[propKey];}
};
const proxy = new Proxy(target, handler);// 访问代理对象会触发拦截
console.log(proxy.name); // 输出"读取属性: name"和"张三"
  • target:被代理的原始对象,Proxy不会修改原对象本身
  • handler:拦截器配置对象,每个属性对应一种操作的拦截逻辑
  • proxy:代理实例,所有对proxy的操作都会先经过handler处理

3. 关键拦截器

get拦截器

在 JavaScript 代码中可以通过多种形式触发并被 get()捕获器拦截到。proxy[property]、proxy.property 或 Object.create(proxy)[property]等操作都会触发基本的 get()操作以获取属性。因此所有这些操作只要发生在
代理对象上,就会触发 get()捕获器。

const target = { foo: 'bar' 
}; 
const handler = { get() { return 'handler override'; } 
}; 
const proxy = new Proxy(target, handler); console.log(target.foo); // bar 
console.log(proxy.foo); // handler override 
console.log(target['foo']); // bar 
console.log(proxy['foo']); // handler override 
console.log(Object.create(target)['foo']); // bar 
console.log(Object.create(proxy)['foo']); // handler override

get拦截器不仅处理常规属性访问,还需要处理数组索引:

const arr = [1, 2, 3];
const arrProxy = new Proxy(arr, {get(target, propKey, receiver) {// 处理数组索引if (typeof propKey === 'string' && /^\d+$/.test(propKey)) {console.log(`访问数组索引: ${propKey}`);}// 处理数组方法if (['push', 'pop'].includes(propKey)) {console.log(`调用数组方法: ${propKey}`);}return Reflect.get(target, propKey, receiver);}
});arrProxy[0]; // 输出"访问数组索引: 0"
arrProxy.push(4); // 输出"调用数组方法: push"
set拦截器的返回值意义

set拦截器需要返回布尔值,表示设置操作是否成功,严格模式下返回false会抛出TypeError:

'use strict';
const user = { age: 20 };
const userProxy = new Proxy(user, {set(target, propKey, value) {if (propKey === 'age' && (value < 0 || value > 150)) {console.error('年龄必须在0-150之间');return false; // 设置失败}return Reflect.set(target, propKey, value);}
});userProxy.age = 200; // 抛出TypeError: 'set' on proxy: trap returned falsish for property 'age'
4. 可撤销代理

有时候可能需要中断代理对象与目标对象之间的联系。对于使用 new Proxy()创建的普通代理来
说,这种联系会在代理对象的生命周期内一直持续存在。
Proxy 也暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的
操作是不可逆的。撤销代理之后再调用代理会抛出 TypeError。

const target = { foo: 'bar' 
}; 
const handler = { get() { return 'intercepted'; } 
}; 
//在实例化的时候同时获得撤销函数和代理对象
const { proxy, revoke } = Proxy.revocable(target, handler); 
console.log(proxy.foo); // intercepted 
console.log(target.foo); // bar 
revoke(); //撤销代理
console.log(proxy.foo); // TypeError
5.捕获器不变式

根据 ECMAScript 规范,每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”(trap invariant)。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。比如,如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError:

const target = {}; 
Object.defineProperty(target, 'foo', { configurable: false, writable: false, value: 'bar' 
}); 
const handler = { get() { return 'qux'; } 
}; 
const proxy = new Proxy(target, handler); 
console.log(proxy.foo); 

二、Reflect:对象操作的标准化工具

Reflect是一个内置对象,它提供了一组与Proxy拦截器对应的静态方法,旨在规范化JavaScript的对象操作。

1. Reflect的设计理念

  • 函数式接口:将原本属于运算符的操作(如indelete)转换为函数调用
  • 统一返回值:操作成功返回true,失败返回false(与Proxy拦截器的返回值要求一致)
  • 错误处理:操作失败时返回false而非抛出异常,简化错误处理
  • this绑定:所有方法都接受receiver参数,正确处理this指向

2. Reflect与Object方法的对比

操作Object方法Reflect方法区别
获取属性obj[prop]Reflect.get(obj, prop)Reflect可指定receiver
设置属性obj[prop] = valueReflect.set(obj, prop, value)Reflect返回操作结果
属性检查prop in objReflect.has(obj, prop)函数式调用
删除属性delete obj[prop]Reflect.deleteProperty(obj, prop)Reflect返回操作结果
定义属性Object.defineProperty()Reflect.defineProperty()Reflect返回布尔值

3. Reflect在Proxy中的关键作用

在Proxy拦截器中,使用Reflect而非直接操作target有三个重要原因:

  1. 正确维护this指向
const obj = {name: "张三",getSelf() {return this;}
};// 不使用Reflect的情况
const badProxy = new Proxy(obj, {get(target, propKey) {return target[propKey]; // this会指向target而非proxy}
});// 使用Reflect的情况
const goodProxy = new Proxy(obj, {get(target, propKey, receiver) {return Reflect.get(target, propKey, receiver); // this指向proxy}
});console.log(badProxy.getSelf() === badProxy); // false
console.log(goodProxy.getSelf() === goodProxy); // true
  1. 传播操作状态:Reflect方法的返回值正好满足Proxy拦截器对返回值的要求

  2. 保持默认行为:确保在自定义逻辑之外,对象行为与原生保持一致

http://www.dtcms.com/a/394576.html

相关文章:

  • 浅解Letterbox算法
  • 【Triton 教程】triton_language.permute
  • JavaScript洗牌算法实践
  • 掌握timedatectl命令:Ubuntu 系统时间管理指南
  • 【RT Thread】RTT内核对象机制详解
  • Seata分布式事务
  • 用例图讲解
  • makefile原理
  • AUTOSAR CP开发流程总结
  • 通过VNC实现树莓派远程桌面访问
  • linux信号done
  • BeanUtils.copyProperties 映射规则详解
  • 物联网 frid卡控制
  • LeetCode刷题记录----322.零钱兑换(Medium)
  • 2015/07 JLPT听力原文 问题四
  • Redis集群实验
  • 昇腾生态双支柱:MindSpore 与 CANN 的全栈技术解析
  • YOLO系列——实时屏幕检测
  • 牛客算法基础noob49 上三角矩阵判定
  • autosar 中OS模块理解
  • 通俗范畴论17.2 向量空间的对偶与双对偶
  • huggingface_hub 安装部署问题汇总
  • 在我的Java项目中为什么使用AllArgsConstructor注解注入的方式启动报错了:
  • π0:一个 VLA 流匹配模型用于通用机器人控制(又称 pi0)
  • Information theorem-Entropy
  • 编译原理实验报告——词法分析程序
  • 整体设计 完整的逻辑链条 之4 认知逻辑视角 —— 前序驱动的认知演进体系 之2
  • C/C++正则表达式PCRE2库
  • 基于python大数据的声乐信息分类评测系统
  • 永磁同步电机无速度算法--改进型超螺旋滑模观测器