定义与概念
JavaScript中的Proxy与Reflect是ES6中引入的新特性,它们可以帮助我们更高效地控制对象。
代理(Proxy)是一种设计模式,它允许我们在访问对象的同时,添加一些额外的操作。代理对象与被代理对象实现相同的接口,代理对象会接受并处理所有对被代理对象的访问请求。
代理是对象通过一个代理对象来控制对原对象的读取、设置、调用及其他操作,并对这些操作进行预处理或附加操作,主要用于拦截对象
反射(Reflection)是指程序可以在运行时获取、操作、修改它本身的状态或行为。反射是一种动态获取类型信息和操作类型的能力,可以在运行时动态调用类中的方法或属性。
反射可以使我们知晓(获取)对象详情,操控对象的成员(属性),在调用对象的方法时加入额外的逻辑,主要用于操作对象
在Java中,反射与代理可以通过reflect以及其中的Proxy类与InvocationHandler接口实现代理,通过reflect实现反射;而在C++中则是用继承和虚函数实现代理模式,使用模板和元编程修改检查结构达到反射。代理和反射通常都是编译时的概念,在编译阶段就已经确定了代理和反射的具体实现方式
而JS则是在运行时动态地创建代理和使用反射,并且提供了类似的概念和实现:全局的Proxy与Reflect对象
属性及函数
Proxy
使用new Proxy实例化时需要传入以下两个参数
- target:被代理的目标对象。
- handler:定义代理行为的对象。
handler参数中有一些钩子函数,在代理对象发生变化时会触发:
- get:在读取代理对象的属性时
- set:在对代理对象的属性进行赋值时
- has:在使用 in 运算符检查代理对象是否具有某个属性时
- deleteProperty:在删除代理对象的属性时
- apply:当前代理对象为函数,在调用代理对象时
- construct:在使用 new 运算符创建代理对象的实例时
- getOwnPropertyDescriptor:在获取代理对象的属性描述符时
- defineProperty:在定义代理对象的属性时
- getPrototypeOf:在获取代理对象的原型时
- setPrototypeOf:在设置代理对象的原型时
- isExtensible:在检查代理对象是否可扩展时
- preventExtensions:在防止代理对象的扩展时
- ownKeys:在返回代理对象的所有键时
Reflect
Reflect对象中的函数与上述handler中的钩子函数是一一对应的
- get:用于读取一个对象的属性值
- set:用于设置一个对象的属性值
- has:用于判断一个对象是否有某个属性
- deleteProperty:用于删除一个对象的属性
- apply:当前代理对象为函数,用于调用当前对象的方法
- construct:用于通过构造函数创建一个新的对象实例
- getOwnPropertyDescriptor:用于读取一个对象的自身属性描述对象
- defineProperty: 用于为一个对象定义一个属性
- getPrototypeOf:用于读取一个对象的原型对象
- setPrototypeOf:用于设置一个对象的原型对象
- isExtensible:用于判断一个对象是否可扩展
- preventExtensions: 用于防止一个对象被扩展
- ownKeys:用于读取一个对象的所有自身属性的键名
使用场景
以下代码可以触发所有的钩子函数
const proxyFactory = (target, opts) => { return new Proxy(target, { set: (target, propertyKey, value, receiver) => { // console.log(target, propertyKey, value, receiver); console.log("执行了set"); return Reflect.set(target, propertyKey, value); }, get: (target, property, receiver) => { // console.log(target, property, receiver); console.log("执行了get"); return Reflect.get(target, property, receiver); }, has: (target, property) => { // console.log(target, property); console.log("执行了has"); return Reflect.has(target, property); }, deleteProperty: (target, property) => { // console.log(target, property); console.log("执行了deleteProperty"); return Reflect.deleteProperty(target, property); }, apply: (target, thisArg, argumentsList) => { // console.log(target, thisArg, argumentsList); console.log("执行了apply"); return Reflect.apply(target, thisArg, argumentsList); }, construct: (target, argumentsList, newTarget) => { // console.log(target, argumentsList, newTarget); console.log("执行了construct"); return Reflect.construct(target, argumentsList, newTarget); }, getOwnPropertyDescriptor: (target, property) => { // console.log(target, property); console.log("执行了getOwnPropertyDescriptor"); return Reflect.getOwnPropertyDescriptor(target, property); }, defineProperty: (target, property, descriptor) => { // console.log(target, property, descriptor); console.log("执行了defineProperty"); return Reflect.defineProperty(target, property, descriptor); }, getPrototypeOf: (target) => { // console.log(target); console.log("执行了getPrototypeOf"); return Reflect.getPrototypeOf(target); }, setPrototypeOf: (target, prototype) => { // console.log(target, prototype); console.log("执行了setPrototypeOf"); return Reflect.setPrototypeOf(target, prototype); }, isExtensible: (target) => { // console.log(target); console.log("执行了isExtensible"); return Reflect.isExtensible(target); }, preventExtensions: (target) => { // console.log(target); console.log("执行了preventExtensions"); return Reflect.preventExtensions(target); }, ownKeys: (target) => { // console.log(target); console.log("执行了ownKeys"); return Reflect.ownKeys(target); }, ...opts, }); }; const obj = { name: "张三", age: 20, }; const fn = function () { return "hello"; }; const __obj = proxyFactory(obj); const __fn = proxyFactory(fn); // apply只有当当前代理对象为函数时才会执行 const init = () => { // set; __obj.name = "李四"; // get; __obj.name; // has; "name" in __obj; // deleteProperty; delete __obj.age; // apply; __fn(); // construct; new __fn(); // getOwnPropertyDescriptor; Object.getOwnPropertyDescriptor(__obj, "name"); // defineProperty; Object.defineProperty(__obj, "name", { value: "王五", }); // getPrototypeOf; Object.getPrototypeOf(__obj); // setPrototypeOf; Object.setPrototypeOf(__obj, null); // isExtensible; Object.isExtensible(__obj); // preventExtensions; Object.preventExtensions(__obj); // ownKeys; Object.getOwnPropertyNames(__obj); console.log(__obj, obj); }; init();
如何实现
实现过程
了解了代理和反射的概念和用法,我们可以尝试使用ES5的语法实现一下对象属性的增删改查
首先是反射
var __Reflect = { set(target, prop, value) { target[prop] = value; }, get(target, prop) { return target[prop]; }, defineProperty(target, property, descriptor) { return Object.defineProperty(target, property, descriptor); }, deleteProperty(target, property) { return delete target[property]; }, };
然后是代理
function __Proxy(target, handler) { var __target = {}; this.target = target; this.handler = handler; this.init(__target); return __target; } __Proxy.prototype = { init(__target) { this.readWrite(__target); Object.__defineProperty = this.defineProperty.bind(this); Object.__delete = this.deleteProperty.bind(this); }, readWrite(__target) { // 初始化读写函数 var target = this.target; var handler = this.handler; for (const key in target) { Object.defineProperty(__target, key, { configurable: true, set(val) { // 新增/修改 target[key] = val; return handler.set(target, key, val); }, get() { // 读取 return handler.get(target, key); }, }); } }, defineProperty(target, property, descriptor) { // 定义/修改 var __d = this.handler.defineProperty; var fn = typeof __d === "function" ? __d : __Reflect.defineProperty; // 如果钩子函数存在,则执行代理拦截函数 return fn(target, property, descriptor); }, deleteProperty(target, property) { // 删除 var __delete = this.handler.deleteProperty; if (typeof __delete === "function") { return __delete(target, property); // 如果钩子函数存在,则执行代理拦截函数 } return __Reflect.deleteProperty(target, property); }, };
大致介绍一下思路:我的做法是在执行属性读写,对象定义,删除前对属性进行函数拦截,将结果抛出,这里的delete由于是全局关键字执行,所以我在Object中增加了删除属性的函数用于模拟delete操作,此外还重写了一下defineProperty函数,用作对象劫持
运行效果
var obj = { name: "张三", age: 20, }; var proxy = new __Proxy(obj, { set(target, prop, value) { console.log("set"); return __Reflect.set(target, prop, value); }, get(target, prop) { console.log("get"); return __Reflect.get(target, prop); }, defineProperty(target, property, descriptor) { console.log("defineProperty"); return __Reflect.defineProperty(target, property, descriptor); }, deleteProperty(target, property) { console.log("deleteProperty"); return __Reflect.deleteProperty(target, property); }, }); // set proxy.name = "李四"; // get console.log(proxy.name); // defineProperty Object.__defineProperty(proxy, "name", { value: "王五", });// 使用自定义的defineProperty,劫持对象操作 // delete Object.__delete(proxy, "name");// 这个函数是模拟delete关键字的操作 console.log(proxy.name, obj.name);
应用限制及优点
限制:
- 上面我们也说了代理和反射是ES6新增的两个对象,兼容性上会有一些折扣;
- 此外使用反射操作对象和直接操作对象还是有区别的,使用反射操作对象会有性能的损失;
优点:
- 提高了对象的灵活性,监听对象及操作对象变得简易
- 拓展性变高了,可以应对更多针对对象的操作
写在最后
以上就是文章所有内容了,感谢你看到了最后,如果对文章有任何问题欢迎在评论区或私信探讨
最后如果文章对你有帮助,还希望点赞支持一下,谢谢!