前言
关于Vue的响应式实现是Vue原理的重要组成部分,正确理解Vue的响应式更有利于我们理解和使用Vue。
这篇文章就来谈一谈Vue3是如何实现响应式的,此前Vue2实现响应式主要依靠的API是ES5的Object.defineProperty
,我们来用它使对象变成可观察的,再结合依赖收集实现了响应式系统。 具体细节可参考下面的视频:
一、Object.defineProperty
的缺点
- 深度监听需要一次性递归(较影响性能)
- 无法监听新增属性/删除属性(可以通过Vue.set,Vue.delete实现)
- 无法原生监听数组,需要特殊处理
于是Vue3为了解决这些问题,使用ES6的
Proxy
以及Reflect
实现了响应式。Vue3使用ES6语法,这是现代JavaScript的东西,不支持IE浏览器,所以尽早放弃IE吧,赶快去下载Chrome!!!
二、Vue3
响应式实现
2.1 理解ES6 Reflect
假设我们现在有一个商品,它拥有price
和quantity
属性。
let product = { price: 5, quantity: 2 } 复制代码
现在我们有三种方法打印出对象的属性:
- 首先,我们可以使用典型的“点”表示法
console.log('quantity is ' + product.quantity) 复制代码
- 我们也可以用括号表示法
console.log('quantity is ' + product['quantity']) 复制代码
- 最后我们还可以使用
ES6 Reflect
,就像你看到的。
console.log('quantity is '+ Reflect.get(product, 'quantity')). 复制代码
正如你所看到的,三个都有效,但是Reflect有一种超能力🤷♀️,在我们理解完
Proxy
之后,我会展示给你看。
2.2 理解ES6 Proxy
Proxy
是另一个对象的占位符,默认情况下对该对象进行委托。
如果我创建一个proxiedProduct
,然后声明一个Proxy
,然后我调用proxiedProduct.quantity
,他就会:
- 它会先调用
Proxy
- 这个
Proxy
再调用产品 - 然后返回到
Proxy
- 返回这个product到控制台日志
- 最后就会打印出
2
let product = { price: 5, quantity: 2 } let proxiedProduct = new Proxy(product, {}) console.log(proxiedProduct.quantity) 复制代码
简单来说就是一个对象委托。
Proxy
中的第二个参数,它叫做处理函数,在这个处理函数中,你可以传递一个诱捕器,它的作用就是可以让我们拦截基本操作,如属性查找
,或枚举
或函数调用
。
接下来我们尝试着实现拦截get
操作。
2.3 使用Proxy
尝试拦截get操作
let product = { price: 5, quantity: 2 } let proxiedProduct = new Proxy(product, { get(){ console.log('Get was called') return 'Not the value' } }) 复制代码
在这个例子中,当我们调用proxiedProduct
中的get时,我们想要改变get的行为。
现在当它被调用时,我们简单的做一下输出,我们来看看运行结果:
好啦,我们现在已经实现了,当获取属性值的时候,实现了自己的get
方法,但是现在做的get方法完全无用,所以我们还是如实的返回值吧~
2.4 使用Proxy
拦截get操作的正确姿势
let product = { price: 5, quantity: 2 } let proxiedProduct = new Proxy(product, { get(target, key){ console.log('Get was called with key= '+ key) return target[key] } }) console.log(proxiedProduct.quantity) 复制代码
我们声明get的两个参数,然后我们输出'Get was called with key= key',我们将使用括号的表示法返回key属性的值,所以当我们调用conosle.log
,它会通过get(target)调用我们的proxiedProduct
,在这种情况下,这个参数target
就是我们传递的product
,我们的键是quantity
,因为我们想得到quantity
,然后就会正确输出,我们来看看结果:
之前我们提到过
Reflect
的作用很大,现在我们尝试在Proxy
里使用Reflect
。
2.5 在Proxy
里使用Reflect
let product = { price: 5, quantity: 2 } let proxiedProduct = new Proxy(product, { get(target, key, receiver){ console.log('Get was called with key= '+ key) // return target[key] return Reflect.get(target,key,receiver) } }) console.log(proxiedProduct.quantity) 复制代码
我们现在会有一个附加参数,称为receiver
,它将传递到我们的Reflect
调用中。
它保证了当我们的对象有继承自其他对象的值或函数时,this
指针能正确的指向使用的对象,这将避免一些我们在Vue2中有的响应式警告,打印结果当然还是不变的:
我们还需要使用
Proxy
来拦截set方法,我们接下里就来实现它~
2.6 使用Proxy
拦截set操作
let product = { price: 5, quantity: 2 } let proxiedProduct = new Proxy(product, { get(target, key, receiver) { console.log('Get was called with key= '+ key) return Reflect.get(target,key,receiver) }, set(target, key, value, receiver) { console.log('Set was called with key = ' + key + 'and value = ' + value) return Reflect.set(target, key, value, receiver) } }) proxiedProduct.quantity = 100 console.log(proxiedProduct.quantity) 复制代码
我们的set
方法接收target,key,value,receiver四个参数,我们将在set
被调用时,打印出我们的key
和value
,那我们在调用Reflect.set
,传递的参数是target,key,value,receiver,我们更改一下quantity
的数量来测试看看:
这样我们就成功的完成了set的拦截,还记得我说过
Proxy
的第二个参数称为处理函数
吗?我们现在可以封装这部分处理函数,让它长的更像Vue3
的源代码
2.7 封装处理函数,使其更像Vue3源码
function reactive(target){ const handler = { get(target, key, receiver) { console.log('Get was called with key= '+ key) return Reflect.get(target,key,receiver) }, set(target, key, value, receiver) { console.log('Set was called with key = ' + key + 'and value = ' + value) return Reflect.set(target, key, value, receiver) } } return new Proxy(target, handler) } let product = reactive({ price: 5, quantity: 2 }) product.quantity = 4 console.log(product.quantity) 复制代码
我们创建一个叫做reactive
的函数,相信你如果使用过Composition API
,你会看起来很熟悉,我们封装我们的get和set方法到常量handler
中,最后我们创建一个新的Proxy
,传递我们的target
以及handler
,现在我们声明一个product
时,我们只需要传递一个对象到reactive
函数中,reactive
函数将返回一个Proxy
,这个Proxy
我们会把它当作原始对象来使用,我们更改quantity
的值,然后控制台打印quantity
的值,当我们执行这段代码,就得到了我们所期望的结果:
最后
⚽读到到这,相信大家对Vue3的响应式实现已经有了更深的认识,虽然Vue3现在仍存在一些浏览器兼容性问题,但是我认为不久后,IE会被淘汰,Vue3将取代Vue2,因此我们要更加积极的学习Vue3使用及原理,后期我还会更新关于Vue3原理的其他文章~
⚾如果你对这篇文章感兴趣欢迎点赞关注+收藏,更多精彩知识正在等你!😘