🌵理解Vue3响应式原理

918 阅读5分钟

前言

关于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

假设我们现在有一个商品,它拥有pricequantity属性。

let product = { price: 5, quantity: 2 }

现在我们有三种方法打印出对象的属性:

  1. 首先,我们可以使用典型的“点”表示法
console.log('quantity is ' + product.quantity)
  1. 我们也可以用括号表示法
console.log('quantity is ' + product['quantity'])
  1. 最后我们还可以使用ES6 Reflect,就像你看到的。
console.log('quantity is '+ Reflect.get(product, 'quantity')).

正如你所看到的,三个都有效,但是Reflect有一种超能力🤷‍♀️,在我们理解完Proxy之后,我会展示给你看。

2.2 理解ES6 Proxy

Proxy是另一个对象的占位符,默认情况下对该对象进行委托。

如果我创建一个proxiedProduct,然后声明一个Proxy,然后我调用proxiedProduct.quantity,他就会:

  1. 它会先调用Proxy
  2. 这个Proxy再调用产品
  3. 然后返回到Proxy
  4. 返回这个product到控制台日志
  5. 最后就会打印出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的行为。现在当它被调用时,我们简单的做一下输出,我们来看看运行结果:

image.png 好啦,我们现在已经实现了,当获取属性值的时候,实现了自己的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,然后就会正确输出,我们来看看结果:

image.png

之前我们提到过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中有的响应式警告,打印结果当然还是不变的:

image.png

我们还需要使用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被调用时,打印出我们的keyvalue,那我们在调用Reflect.set,传递的参数是target,key,value,receiver,我们更改一下quantity的数量来测试看看:

image.png

这样我们就成功的完成了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的值,当我们执行这段代码,就得到了我们所期望的结果:

image.png

最后

⚽读到到这,相信大家对Vue3的响应式实现已经有了更深的认识,虽然Vue3现在仍存在一些浏览器兼容性问题,但是我认为不久后,IE会被淘汰,Vue3将取代Vue2,因此我们要更加积极的学习Vue3使用及原理,后期我还会更新关于Vue3原理的其他文章~
⚾如果你对这篇文章感兴趣欢迎点赞关注+收藏,更多精彩知识正在等你!😘
🏀GitHub 博客地址: github.com/Awu1227
🏉笔者还有其他专栏,欢迎阅读~
🏐玩转CSS之美
🎱Vue从放弃到入门
🎳深入浅出JavaScript