JavaScript
ECMAScript 新特性
ES6之扩展运算符
JavaScript-堆栈分析
JavaScript 性能优化
JavaScript 异步编程
Promise对象使用回顾
TypeScript 语言
从ES6到ES10的新特性万字大总结
手写Promise源码
JavaScript高级程序设计(第4版)
这20个JavaScript的数组实现方法,一定不要错过!
JavaScript 中 this 关键字有哪些用处?
Proxy 的使用细节
有道无术,术尚可求。有术无道,止于术!
-
+
首页
Proxy 的使用细节
当初在看 `ES6` 的书籍时,我首次接触到了 `Proxy` 这个概念。"这不就是 `Object.defineProperty` 的替代品吗?功能与 `defineProperty` 如出一辙", 那时的我并未深究两者间的差异,毕竟在日常开发中鲜有机会触及这些高级特性。因此,我对这部分内容的了解浅尝辄止,如同翻阅了一本小说一样,匆匆一瞥后便迅速淡忘,只依稀记得 JavaScript 世界中存在这样一项特性。 然而,随着 Vue 3 的发布,Vue 3 的响应式系统基于 `Proxy` 进行了重构,这一变化在网络上引发了广泛的讨论和深入的分析。 **`Proxy` 与 `Object.defineProperty` 之间的区别已成为 Vue 面试的标配**,网络上关于 `Proxy` 与 `Object.defineProperty` 的对比讨论如雨后春笋般涌现。 如今,`Proxy` 已经成为 Vue 3 中不可或缺的一部分。 ### 语法 **Proxy 被用于创建一个对象的代理,以便对其进行操作拦截。** ![img](https://pic3.zhimg.com/80/v2-51cf21978e688bd5eacc9ffeb29bda3e_720w.webp) - target: 被代理的原始对象。 - handler: 配置拦截操作的对象。 **当 handler 为 `{}` 时,所有对代理对象的操作默认转给原始对象 target 上。** ![img](https://pic4.zhimg.com/80/v2-47bb237accf2e440e83a15f47eab33f7_720w.webp) 一般我们常用的拦截操作就是对象的读取和修改的操作。handler 通过 **`get`** 和 **`set`** 捕获器来进行拦截。 ![img](https://pic2.zhimg.com/80/v2-4a3d0c719221e07239e1c6dac1820801_720w.webp) 代理一个包含 `getter` 属性的对象 ![img](https://pic1.zhimg.com/80/v2-64d7d2903a1c0b44d9de812cd3985890_720w.webp) 当这代理对象是另一个对象的原型时,this 的指向会指向被代理的原始对象,这可能会在原型链中引发问题。 ![img](https://pic3.zhimg.com/80/v2-9a8ca10255a63f559de2f5696700ca9e_720w.webp) **Reflect 就是用来解决这个问题。** ### Reflect 的作用 Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法,例如常用的 `get` 和 `set` 操作: ![img](https://pic3.zhimg.com/80/v2-1712b089258b2aa6b1806a32da42e216_720w.webp) 这里差异在第三个参数 `receiver` 上,它用于指定 `getter` 中的 `this` 对象,以及 `get` 和 `set` 拦截器的第三个参数。 ![img](https://pic3.zhimg.com/80/v2-47b2ec38660f04894ccab78dd8039692_720w.webp) 读取 `name` 属性时触发 `get` 捕获器执行 `Reflect.set(target, prop, receiver)` 代码,将 `get name` 中的this指向了 `receiver` 也就是 `admin` ,因此打印的值是 'Admin'。 ### Proxy的工作原理 **创建代理对象时指定的拦截函数,实际上是用来自定义代理对象本身的内部方法和行为的,而不是用来指定被代理对象的内部方法和行为的。** ### 其他拦截操作 ### in 拦截 要拦截 `in` 操作,可以通过 `has` 捕获器。 ![img](https://pic2.zhimg.com/80/v2-9102234364fe9444f8780c1da6b43891_720w.webp) ### for in 拦截 `for in` 操作的拦截要通过 `ownKeys` 捕获器 ![img](https://pic3.zhimg.com/80/v2-36f4d76be652b930203dfc2d1ea057c6_720w.webp) ### delete 拦截 `delete` 操作的拦截要通过 `deleteProperty` 捕获器 ![img](https://pic4.zhimg.com/80/v2-15f19f39f59b11b82b397569e86529ef_720w.webp) ### 函数 拦截 函数拦截要通过 `apply` 捕获器 ![img](https://pic1.zhimg.com/80/v2-50121b865f9636774c2fc01c50d07670_720w.webp) ### 数组代理 代理数组不需要对内置操作数组的方法重写,会自动作用在原数组对象上。虽然不用重写,但需要注意 `get` 和 `set` 捕获器的触发顺序。 ### push ![img](https://pic1.zhimg.com/80/v2-fbb4ca6f6e20c4334137a58f89b8433c_720w.webp) `push` 操作执行流程是将 push 的参数遍历执行 `Set` 操作,然后再设置 `length` 属性。 ![img](https://pic1.zhimg.com/80/v2-6cd34a4e94d6b1627e2c9f929d324dcc_720w.webp) 打印结果可以看出: - `push` 操作首先会读取数组的 `push` 属性,而执行 `get` 操作,打印 `get push`。 - 然后再读取数组 `length` 属性,而再一次执行 `get` 操作,打印 `get lenght`。 - 然后遍历将 `push` 的参数添加到数组末尾而执行 `set` 操作,打印 `set 0:1` 和 `set 1:2`。 - 最后还需要修改 `length` 的值,而执行 `set` 操作,打印 `set length:2`。 `push` 的操作会多次执行 `get` 和 `set`, 在对数组做拦截操作时,如果不清楚 `push` 的执行流程就很容易出现问题。 ### includes 数组的代理对象执行查找 `includes` 操作同样可以作用在原数组对象上。 ![img](https://pic1.zhimg.com/80/v2-9d38ea4dd3e195d02b0beea4c1473a98_720w.webp) 但同样需要注意 `get` 和 `set` 捕获器的触发顺序: ![img](https://pic1.zhimg.com/80/v2-8a4a1a8914f42a7b03b894f380ed33f4_720w.webp) 执行流程: - 读取数组的 `includes` 属性,而执行 `get` 操作,打印 `get includes`。 - 然后再读取数组 `length` 属性,而再一次执行 `get` 操作,打印 `get lenght`。 - 遍历读取数组的项知道找到指定的值,然后返回 true。 ### 遍历数组 常用的遍历数组的方式主要有 `for...of`、`forEach`、`map`、`for...in`。 **for...of** `for...of` 的操作是执行数组对象上 `Symbol.iterator` 函数,要拦截器操作需要 `get` 方法中判断 `key` 值是否是`Symbol.iterator`,再返回自定义的函数即可。 ![img](https://pic4.zhimg.com/80/v2-63991b99b2d68d11ad9dc2c0d151281f_720w.webp) **forEach、map** `forEach` 操作不用单独处理,执行流程和 `includes` 类似。 ![img](https://pic4.zhimg.com/80/v2-f13e6e7f20ea1c26b0b55e0942a7fafb_720w.webp) 先读取 `forEach` 然后再读取 `length` 之后再遍历读取数组中的项。 ### shift 和 unshif 操作的性能问题 `shift`、 `unshif` 操作和 `push`操作不一样,会导致数组中每个项都移动,并且都会触发一次 `set` 捕获器,在使用 `Poxy` 对象代理数组时要注意操作时的性能问题。 ![img](https://pic1.zhimg.com/80/v2-3dbe93e60ead651feae50fa01d3eb5b8_720w.webp) 打印结果可以看出 `unshif` 的执行流程: - 读取数组对象的 `unshif` 属性,执行 `get` 方法,打印`get unshift`。 - 读取数组的 `length` 属性,执行 `get` 方法,打印 `get length`。 - 依次将数组的项往后移动一个位置。 - 将0设置为数组第一个位置上。 - 设置 `length` 加一。 ### 代理 Map 和 Set 对象 Map 和Set 对象会将项目存储在内部插槽中,而这个内部插槽是只能内置方法直接访问,不能通过 get/set 方法来拦截,因此proxy无法拦截这个内部插槽。 ![img](https://pic2.zhimg.com/80/v2-f3d6ae6797e3ef159ac0b3a751bff749_720w.webp) 因为 `Map.prototype.set` 方法中 this 是 `proxy`,因此访问 this 的内部插槽使不会走代理对象的 `get` 方法,在`proxy` 中无法找到这个内部插槽而报错。 解决方法就是将 this 指向原 Map 对象。 ![img](https://pic3.zhimg.com/80/v2-3b2272791073f8934d97918ac2b2df4a_720w.webp) ### 目前使用Proxy的常用框架 - Vue3 响应式。 - 微前端框架 qiankun 中使用 Poxy 实现 js 沙箱。
Leo
2024年5月26日 11:09
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码