前言
关于Vue的响应式实现是Vue原理的重要组成部分,正确理解Vue的响应式更有利于我们理解和使用Vue。
这篇文章就来谈一谈Vue3是如何实现响应式的,此前Vue2实现响应式主要依靠的API是ES5的Object.defineProperty,我们来用它使对象变成可观察的,再结合依赖收集实现了响应式系统。
具体细节可参考下面的视频:
尤大大教你写Vue
一、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原理的其他文章~
⚾如果你对这篇文章感兴趣欢迎点赞关注+收藏,更多精彩知识正在等你!😘
🏀GitHub 博客地址: github.com/Awu1227 。
🏉笔者还有其他专栏,欢迎阅读~
🏐玩转CSS之美
🎱Vue从放弃到入门
🎳深入浅出JavaScript