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对象有4个意义:
- 「默认行为的一致性」:
Reflect
对象提供了与大多数Proxy
traps 对应的方法,使得在进行对象操作时,可以保持一致的编程模式,且代码的可读性和可维护性更强。 - 「更好的错误处理」:
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']
- 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()