Proxy 和 Reflect
前言:Proxy 和 Reflect是配套出现使用的。虽然Proxy可以不适用Reflect也能实现系统的功能,但是会增加代码复杂度,也增加了代码理解难度。
https://mp.weixin.qq.com/s/G6maIUZONMWHxJG_E1_f-w
Proxy
一、基本语法:
const proxy = new Proxy(target, handler);
target:目标对象,即被代理的对象。handler:处理程序对象,定义了代理对象的方法,用于拦截和定义目标对象的操作。
const target = { name: '小明', age: 18 };
const handler = {
get(target, prop, receiver) {
console.log(`访问了属性:${prop}`);
return target[prop];
},
set(target, prop, value, receiver) {
console.log(`设置了属性:${prop},值为:${value}`);
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 输出:访问了属性:name,小明
proxy.age = 19; // 输出:设置了属性:age,值为:19
console.log(proxy.age); // 输出:访问了属性:age,19
总结:proxy 代理 使得对 target 的操作,全部作用到 handler 同名函数中了。
二、实践中的使用: 开发中,什么情况使用proxy好呢
- 数据绑定与观察者模式:vue3 的响应式就是 基于 proxy 实现的。
- 表单验证: 我们可以使用
Proxy和Reflect实现表单验证,在设置对象属性时进行校验const form = { username: '', age: 0 }; const handler = { set(target, prop, value, receiver) { if (prop === 'age' && (typeof value !== 'number' || value <= 0)) { throw new TypeError('Age must be a positive number'); } return Reflect.set(target, prop, value, receiver); } }; const proxy = new Proxy(form, handler); try { proxy.age = -5; // 抛出错误:Age must be a positive number } catch (e) { console.error(e.message); } proxy.age = 30; // 设置成功 console.log(proxy.age); // 输出:30
- 扩展对象功能:使用
Proxy可以动态地扩展对象功能。例如,可以在现有对象上添加日志记录功能,而不需要修改对象的原始代码。const original = { greet() { console.log('Hello!'); } }; const handler = { apply(target, thisArg, argumentsList) { console.log(`调用了方法:${target.name}`); return Reflect.apply(target, thisArg, argumentsList); } }; const proxy = new Proxy(original.greet, handler); proxy(); // 输出:调用了方法:greet,Hello!
- 方法劫持: 方法劫持可以用于调试、性能监控或权限控制。例如,在调用某个方法前后插入自定义逻辑。
const service = { fetchData() { console.log('Fetching data...'); // 模拟数据获取 } }; const handler = { apply(target, thisArg, argumentsList) { console.log('开始调用 fetchData'); const result = Reflect.apply(target, thisArg, argumentsList); console.log('结束调用 fetchData'); return result; } }; const proxy = new Proxy(service.fetchData, handler); proxy(); // 输出:开始调用 fetchData,Fetching data...,结束调用 fetchData
- API 请求拦截: 我们还可以使用
Proxy和Reflect实现 API 请求的拦截和日志记录const api = { fetchData(endpoint) { console.log(`Fetching data from ${endpoint}`); // 模拟 API 请求 return { data: 'Sample Data' }; } }; const handler = { apply(target, thisArg, argumentsList) { console.log(`调用了方法:${target.name}`); return Reflect.apply(target, thisArg, argumentsList); } }; const proxy = new Proxy(api.fetchData, handler); const data = proxy('/api/data'); // 输出:调用了方法:fetchData Fetching data from /api/data console.log(data); // 输出:{ data: 'Sample Data' }
Reflect
一、为什么要使用Reflect这个全局对象:https://blog.csdn.net/qdmoment/article/details/91413951
Reflect 是 ES6 为了操作对象引入的 API 。
1、将 Object 对象一些内部的方法,放到Reflect对象上。比如:Object.defineProperty
说明:现阶段这些方法存在于Object和Reflect对象上,未来只存在于Reflect对象上。【个人猜测:可能这样处理对对象的操作更优雅】
意义:也就是说,从Reflect对象上可以拿到语言内部的方法。(如,读写对象属性、对象构造函数等)
2、操作对象时出现报错返回false,而不是报错:【可以避免过多的使用 try...catch...,尤其是在框架层面上优势更明显】
说明:比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure }
3、让操作对象的编程变为函数式编程
说明:老写法有的是命令式编程,比如下面这个例子
// 老写法 'assign' in Object // true // 新写法 Reflect.has(Object, 'assign') // true
4、保持和proxy对象的方法一一对应 https://www.zhihu.com/question/460133198【有说明为什么要Reflect来实现。就是 简化了捕获器中,透传语义的写法】
说明:Reflect主要是和Proxy配对使用的。只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。【即 proxy对象上的操作,透传到源对象上操作】
Proxy(target, { set: function(target, name, value, receiver) { return Reflect.set(...arguments); // 这里 使用Reflect的方法,和 Proxy 对象里面 捕获器 的方法是对应的。 } });
总结:Reflect 的核心价值是规范化对象操作,通过函数式接口让操作更统一、可控,同时与 Proxy 完美配合,简化拦截逻辑,是 ES6 中对对象模型的重要补充。
综上所述,Reflect对象有4个意义:
- 「默认行为的一致性」:
Reflect对象提供了与大多数Proxytraps 对应的方法,使得在进行对象操作时,可以保持一致的编程模式,且代码的可读性和可维护性更强。 - 「更好的错误处理」:
Reflect方法返回一个布尔值,可以清晰地指示操作是否成功,这使得错误处理更加直观。(更安全的操作 对象,不会静默操作)
Reflect 方法在遇到非法操作时会抛出适当的异常,如TypeError或RangeError,而不是默默地失败。这种严格的错误处理有助于在开发阶段尽早发现潜在问题。 - 「函数式编程风格」:
Reflect方法接受目标对象作为第一个参数,这允许你以函数式编程风格处理对象操作。 - 「接收者(receiver)参数」:
Reflect方法通常接受一个接收者参数,这允许你在调用方法时明确指定this的值,这在实现基于原型的继承和自定义this绑定时非常有用。
Reflect 实践中的作用: Reflect 可以看作弥补 Object 对象上不足,而创造出来的。所以 Reflect具有的属性方法都是 Object 内置的属性方法。
- 用Reflect取代 in、delete 这种操作对象的方式。
- 返回值一致性:Reflect 方法通常返回一个布尔值来表示操作的成功与否,或者返回与操作相关的具体结果。这与一些Object类型上的方法可能返回undefined或静默失败不同,提高了代码的可预测性和调试性。
二、Reflect常用的API:https://www.bilibili.com/video/BV1Su411979o?p=2&spm_id_from=pageDriver
1、Reflect对象一共有13个静态方法:
-
- Reflect.apply(target, thisArg, args) 相当于 Function.prototype.apply(thisArg, args)
- Reflect.construct(target, args) New 构造函数()
- Reflect.get(target, name, receiver) obj.prop 或 obj['prop']
receiver的作用就是改变方法中this的指向,指向receiver对象。如代理拦截器中,返回代理对象的属性,而不是原始对象的属性。 - Reflect.set(target, args, value, receiver)
- Reflect.defineProperty()
- Reflect.deleteProperty() delete obj.prop 或 delete obj['prop']
- Reflect.has() 'prop' in obj
- Reflect.ownKeys()
- Reflect.isExtensible()
- Reflect.preventExtensions()
- Reflect.getOwnPropertyDescriptor()
- Reflect.getPrototypeOf()
- Reflect.setPrototypeOf()
三、直接对象操作还是Reflect操作
决策指南
- 优先直接对象操作:简单属性读写、方法调用、非元编程场景;需快速开发、代码可读性优先的场景。
- 优先Reflect:
- 需要与
Proxy配合的元编程场景; - 需要明确操作结果(成功/失败)的边界情况;
对象的元编程操作,失败时会静默,无法感知。使用Reflect可以明确给出操作结果。如 delete obj.prop 使用 Reflect.deleteProperty() 更可控。
- 需要操作符函数化(如动态调用、函数式编程);
- 需要处理不可扩展、密封、冻结对象的场景;
- 需要标准化返回值(如
boolean)的场景。
- 需要与
Proxy中为什么要使用Reflect处理
主要原因是,在Proxy中访问原始对象属性可能存在一些问题,需要做各种逻辑处理。而使用Reflect可以避免这种逻辑错误的出现。
具体可以从以下几个角度理解:
-
const obj = Object.freeze({ name: 'foo' }); // 冻结对象,属性不可修改 const proxy = new Proxy(obj, { set(target, key, value, receiver) { // 用 Reflect.set 判断原始操作是否成功 const success = Reflect.set(target, key, value, receiver); if (!success) { console.error(`无法修改属性 ${key}`); } return success; } }); proxy.name = 'bar'; // 输出:"无法修改属性 name"(因对象被冻结)
简单说:直接访问 target 像“手动操作”需要自己处理各种边界情况; Reflect.get像“原生工具”,能一键按 JS 规范完成操作(内部做好了边界处理),还能适配代理的上下文需求。

浙公网安备 33010602011771号