二、Proxy 和 Reflect
在上一节内容中,介绍了如何在数据发生变化后,自动更新数据,但存在的问题是,每次需要手动通过触发 track()
函数搜集依赖,通过 trigger()
函数执行所有副作用,达到数据更新目的。
这一节将来解决这个问题,实现这两个函数自动调用。
1. 如何实现自动操作
这里我们引入 JS 对象访问器的概念,解决办法如下:
- 在读取(GET 操作)数据时,自动执行
track()
函数自动收集依赖; - 在修改(SET 操作)数据时,自动执行
trigger()
函数执行所有副作用;
那么如何拦截 GET 和 SET 操作?接下来看看 Vue2 和 Vue3 是如何实现的:
- 在 Vue2 中,使用 ES5 的 Object.defineProperty() 函数实现;
- 在 Vue3 中,使用 ES6 的 Proxy 和 Reflect API 实现;
需要注意的是:Vue3 使用的 Proxy
和 Reflect
API 并不支持 IE。
Object.defineProperty() 函数这边就不多做介绍,可以阅读文档,下文将主要介绍 Proxy 和 Reflect API。
2. 如何使用 Reflect
通常我们有三种方法读取一个对象的属性:
- 使用
.
操作符:leo.name
; - 使用
[]
:leo['name']
; - 使用
Reflect
API:Reflect.get(leo, 'name')
。
这三种方式输出结果相同。
3. 如何使用 Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。语法如下:
参数如下:
- target : 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
- handler : 一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
我们通过官方文档,体验一下 Proxy API:
这样就保证我们每次在读取 proxiedProduct.price
都会执行到其中代理的 get 处理函数。其过程如下:
然后结合 Reflect 使用,只需修改 get 函数:
输出结果还是一样。
接下来增加 set 函数,来拦截对象的修改操作:
这样便完成 get 和 set 函数来拦截对象的读取和修改的操作。为了方便对比 Vue 3 源码,我们将上面代码抽象一层,使它看起来更像 Vue3 源码:
这样输出结果仍然不变。
4. 修改 track 和 trigger 函数
通过上面代码,我们已经实现一个简单 reactive()
函数,用来将普通对象转换为响应式对象。但是还缺少自动执行 track()
函数和 trigger()
函数,接下来修改上面代码: