本篇内容介绍了“如何使用Proxy对象”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联公司坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都做网站、成都网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的旌阳网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
一、聊一聊代理
在日常工作中,相信挺多小伙伴都用过 Web 调试代理工具,比如 Fiddler 或 Charles。通过使用 Web 调试代理工具,我们可以抓取 HTTP/HTTPS 协议请求,还可以手动修改请求参数和响应结果。不仅如此,在调试线上问题时,利用 Web 调试代理工具,你还可以把线上压缩混淆过 的 JS 文件映射成本地 未压缩混淆过 的 JS 文件。
在简单介绍了 Web 调试代理工具的基本功能之后,我们来看一下使用 Web 调试代理工具的 HTTP 请求流程:
通过上图可知,在引入 Web 调试代理工具之后,我们发起的 HTTP 请求都会通过 Web Proxy 进行转发和处理。增加了 Web Proxy 代理层,让我们能够更好地控制 HTTP 请求的流程。对于单页应用程序来说,当从服务器获取数据之后,我们就会读取相应的数据在页面上显示出来:
以上流程与浏览器直接从服务器获取数据类似:
为了能够灵活控制 HTTP 请求的流程,我们增加了的 Web Proxy 层。那么我们能否控制数据对象的读取流程呢?答案是可以的,我们可以利用 Web API,比如 Object.defineProperty 或 Proxy API。在引入 Web API 之后,数据的访问流程如下图所示:
接下来,阿宝哥将重点介绍 Proxy API,它可是 Vue3 实现数据响应式幕后的 “功臣” 哟。对它感兴趣的小伙伴,跟阿宝哥一起学起来吧。
二、Proxy 对象简介
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。Proxy 的构造函数语法为:
const p = new Proxy(target, handler)
相关的参数说明如下:
target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。
在介绍 Proxy 对象的使用示例前,我们先来了解一下它的兼容性:
(图片来源:https://caniuse.com/?search=Proxy)
由上图可知,Proxy API 的兼容性并不是很好,所以大家在使用的时候要注意其兼容性问题。
2.1 Proxy 对象使用示例
了解完 Proxy 构造函数,我们来看一个简单的例子:
const man = { name: "阿宝哥", }; const proxy = new Proxy(man, { get(target, property, receiver) { console.log(`正在访问${property}属性`); return target[property]; }, }); console.log(proxy.name); console.log(proxy.age);
在以上示例中,我们使用了 Proxy 构造函数为 man 对象,创建了一个代理对象。在创建代理对象时,我们定义了一个 get 捕获器,用于捕获属性读取的操作。 捕获器的作用就是用于拦截用户对目标对象的相关操作,在这些操作传播到目标对象之前,会先调用对应的捕获器函数,从而拦截并修改相应的行为。
在设置了 get 捕获器之后,当成功运行以上的示例代码,控制台会输出以下结果:
正在访问name属性 阿宝哥 正在访问age属性 undefined
通过观察以上输出结果,我们可以发现 get 捕获器 不仅可以拦截已知属性的读取操作,也可以拦截未知属性的读取操作。在创建 Proxy 对象时,除了定义 get 捕获器 之外,我们还可以定义其他的捕获器,比如 has、set、delete、apply 或 ownKeys 等。
2.2 handler 对象支持的捕获器
handler 对象支持 13 种捕获器,这里阿宝哥只列举以下 5 种常用的捕获器:
handler.get():属性读取操作的捕获器。
handler.set():属性设置操作的捕获器。
handler.deleteProperty():delete 操作符的捕获器。
handler.ownKeys():Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕获器。
handler.has():in 操作符的捕获器。
需要注意的是,所有的捕获器是可选的。如果没有定义某个捕获器,那么就会保留源对象的默认行为。 看完上面的捕获器介绍,是不是觉得 Proxy 对象很强大。
三、Reflect 对象简介
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。
在介绍 Reflect 对象的使用示例前,我们先来了解一下它的兼容性:
(图片来源:https://caniuse.com/?search=Reflect)
3.1 Reflect 对象使用示例
const man = { name: "阿宝哥", city: "Xiamen", }; console.log(Reflect.set(man, "sex", 1)); // true console.log(Reflect.has(man, "name")); // true console.log(Reflect.has(man, "age")); // false console.log(Reflect.ownKeys(man)); // [ 'name', 'city', 'sex' ]
除了示例中介绍的 set、has 和 ownKeys 方法之外,Reflect 对象还支持 get、defineProperty 和 deleteProperty 等方法。下面阿宝哥将简单介绍 Reflect 对象所支持的一些静态方法。
3.2 Reflect 对象支持的静态方法
Reflect 的所有属性和方法都是静态的,该对象提供了与 Proxy handler 对象相关的 13 个方法。同样,这里阿宝哥只列举以下 5 个常用的方法:
Reflect.get(target, propertyKey[, receiver]):获取对象身上某个属性的值,类似于 target[name]。
Reflect.set(target, propertyKey, value[, receiver]):将值赋值给属性的函数。返回一个布尔值,如果更新成功,则返回 true。
Reflect.deleteProperty(target, propertyKey):删除 target 对象的指定属性,相当于执行 delete target[name]。
Reflect.has(target, propertyKey):判断一个对象是否存在某个属性,和 in 运算符的功能完全相同。
Reflect.ownKeys(target):返回一个包含所有自身属性(不包含继承属性)的数组。
在实际的 Proxy 使用场景中,我们往往会结合 Reflect 对象提供的静态方法来实现某些特定的功能。为了让大家能够更好地理解并掌握 Proxy 对象,接下来的环节,阿宝哥将列举 Proxy 对象的 6 个使用场景。
四、Proxy 使用场景
这里我们先来介绍 Proxy 对象的第一个使用场景 —— 增强型数组。
4.1 增强型数组
定义 enhancedArray 函数
function enhancedArray(arr) { return new Proxy(arr, { get(target, property, receiver) { const range = getRange(property); const indices = range ? range : getIndices(property); const values = indices.map(function (index) { const key = index < 0 ? String(target.length + index) : index; return Reflect.get(target, key, receiver); }); return values.length === 1 ? values[0] : values; }, }); function getRange(str) { var [start, end] = str.split(":").map(Number); if (typeof end === "undefined") return false; let range = []; for (let i = start; i < end; i++) { range = range.concat(i); } return range; } function getIndices(str) { return str.split(",").map(Number); } }
使用 enhancedArray 函数
const arr = enhancedArray([1, 2, 3, 4, 5]); console.log(arr[-1]); //=> 5 console.log(arr[[2, 4]]); //=> [ 3, 5 ] console.log(arr[[2, -2, 1]]); //=> [ 3, 4, 2 ] console.log(arr["2:4"]); //=> [ 3, 4] console.log(arr["-2:3"]); //=> [ 4, 5, 1, 2, 3 ]
由以上的输出结果可知,增强后的数组对象,就可以支持负数索引、分片索引等功能。除了可以增强数组之外,我们也可以使用 Proxy API 来增强普通对象。
4.2 增强型对象
创建 enhancedObject 函数
const enhancedObject = (target) => new Proxy(target, { get(target, property) { if (property in target) { return target[property]; } else { return searchFor(property, target); } }, }); let value = null; function searchFor(property, target) { for (const key of Object.keys(target)) { if (typeof target[key] === "object") { searchFor(property, target[key]); } else if (typeof target[property] !== "undefined") { value = target[property]; break; } } return value; }
使用 enhancedObject 函数
const data = enhancedObject({ user: { name: "阿宝哥", settings: { theme: "dark", }, }, }); console.log(data.user.settings.theme); // dark console.log(data.theme); // dark
以上代码运行后,控制台会输出以下代码:
dark dark
通过观察以上的输出结果可知,使用 enhancedObject 函数处理过的对象,我们就可以方便地访问普通对象内部的深层属性。
4.3 创建只读的对象
创建 Proxy 对象
const man = { name: "semlinker", }; const handler = { set: "Read-Only", defineProperty: "Read-Only", deleteProperty: "Read-Only", preventExtensions: "Read-Only", setPrototypeOf: "Read-Only", }; const proxy = new Proxy(man, handler);
使用 proxy 对象
console.log(proxy.name); proxy.name = "kakuqo";
以上代码运行后,控制台会输出以下代码:
semlinker proxy.name = "kakuqo"; ^ TypeError: 'Read-Only' returned for property 'set' of object '#