0


莽村李青都看得懂的Vue响应式原理

Vue响应式原理

八股文序

开篇来一段大家都会背诵的八股文。

某面试官: 请你简要介绍一下Vue的响应式原理。

答:Vue利用发布订阅模式结合Object.defineProperty劫持对象的get和set方法来实现响应式。

某面试官追问:你知道什么是依赖吗?

答:。。。

某面试官再次追问:请你简述一下依赖收集的过程。

答:。。。

违背老祖宗的决定将Vue响应式原理公众于世

我相信大部分人对于vue的响应式的了解程度也和上面八股文篇描写的差不多。所以我做了一个违背老祖宗的决定那就是

将Vue的响应式原理公众于世!!!!

因为是介绍响应式原理,所以我们以Vue2的一个最最简单的实例来入门。
在这里插入图片描述
从图可知,即使我们什么原理都不知道,也应该新建一个Vue类。

classVue{constructor({ el, data }){this.el = document.querySelector(el)this.data = data
            }}

响应式数据(Observe篇)

下一步,把普通对象变成响应式对象。如何实现呢? 八股文已经给出了答案。
在这里插入图片描述
在Vue的构造器中增加一行

 observe(data)
classVue{constructor({ el, data }){this.el = document.querySelector(el)this.data = data
                // 执行observe函数将普通对象转换成响应式对象observe(data)}}

那么很显然,observe函数就是将普通对象变成响应式对象的函数。这一块并不难,所以这里直接给出代码,注意看注释

// observe 函数,observe中文是观察,观察一个普通对象 使它变成响应式的constobserve=(target)=>{// 如果不是对象数据类型,只是普通类型那么变成响应式if(typeof target !=='object')return// 新建一个Observe类 实现响应式的具体逻辑newObserve(target)}classObserve{constructor(data){
                Object.entries(data).forEach(item=>{
                    Object.defineProperty(data, item[0],{get(){// 劫持对象的get方法
                            console.log(`看来你想获取${item[0]}的值`)return item[1]},set(newValue){// 劫持对象的set方法  // 这里要注意的是如果新值是对象类型 不要忘记响应化if(typeof newValue ==='object')observe(newValue)
                            item[1]= newValue
                        }})})}}

没错 将一个普通对象变成响应式对象就是这么简单,这一小节已经完成了。

dom更新(Wacther篇)

由于本文主要介绍的是响应化原理,那么一些虚拟dom渲染的过程就以图带过。我们看到Vue的构造函数中的el属性
在这里插入图片描述
我们可以从el属性的值获取到实际dom结构。
我以图的形式来描述dom和watcher的关系
在这里插入图片描述
由图中的对应关系可得。每一个响应式变量都会对应一个watcher。
那么以我们本文开始的🌰做进一步解释

<divid="app">
        {{name}}
  </div>

新建一个watcher实例需要三个属性,
1、监听的响应式对象(很好,在上一小节中我们已经完成了)
2、监听的key (从图文关系可知,每一个key对应一个watcher,所以需要传入key)
3、回调函数,也就是监听的响应式对象的key值发生变化的时候,需要执行的回调,这个回调函数就是dom更新函数。以保证数据改变—>回调执行—>dom更新

根据目前的分析,一个初步的Wather类已经写好了

classWatcher{constructor(target, key, callBack){this.target = target
                this.key = key
                this.callBack = callBack
            }// 传入的callback就是dom的更新函数,updateDom就是执行这个函数upDateDom(){this.callBack()}}

那么现在问题来了?如何让watcher观察的key和响应式数据中的key一一对应上呢?
八股文早已给出了答案
在这里插入图片描述
新建一个Dep类(很关键哦!),Dep是dependence的缩写,依赖的意思。那么他们的对应关系依然用图说话
在这里插入图片描述
由图可知,dom中的插值表达式和watcher是一对一的关系,但是响应式数据和watcher是一对多的关系
我们可以这样理解,不止一个dom节点使用到了name属性,所以name更新要通知全部的watcher更新它们的dom所以是一对多的关系。

新建一个发布订阅模式。

classDep{constructor(){// 保存响应式数据的所有依赖,这里的依赖就是watcher实例this.depList =[]}// 由于是一对多的关系,所以要有一个add方法addDep(watcher){this.depList.push(watcher)}// 通知全部的watcher更新视图noticeWatcher(){// 上面已经说过了,每一个依赖都是一个watcher实例// noticeWatcher就说明watcher(订阅者)已经收到了通知// 需要更新domthis.depList.forEach(item=>{
                    item.upDateDom()})}}

由上图可知,响应式变量的每一个key都需要保存使用到它的dom节点(我们称为依赖)
下一小节介绍依赖收集。

依赖收集

如果你坚持到了这一小节,那么你已经完成了 发布者(Dep类),订阅者(Watcher类),响应式数据(Observe)类。已经是梅西进禁区----只差临门一脚了。依赖收集的过程就是将响应式数据关联的依赖(也就是使用到该响应式数据的dom节点)给收集起来。
注意看,这个男人叫小帅,他开始写最关键的代码了。改写watcher类。

classWatcher{constructor(target, key, callBack){this.target = target
                this.key = key
                this.callBack = callBack
                this.getValue()}// 传入的callback就是dom的更新函数,updateDom就是执行这个函数upDateDom(){this.callBack()}getValue(){// 执行getValue函数完成依赖收集this.target[this.key]}}

什么?? 执行getValue的函数就可以完成依赖收集????
以本文的html为例,新建一个watcher实例

var app =newVue({el:'#app',data:{name:'wxs'}})newWatcher(app.data,'name',()=>{
            console.log('假设这是一个dom更新函数!!!')})

当我们新建一个vue实例时,说明app.data已经变成了响应式对象,所以监听者watcher观察的就是这个对象,key为‘name’。由于在watcher的构造函数中执行了函数getValue

this.target[this.key]

那么响应式对象的get方法就被触发了。
不好理解的话看图
在这里插入图片描述
在watcher的

getValue

函数中插一行代码,构造器中加一行代码

getValue(){// 保存当前watcher实例
                Dep.target =this// 执行getValue函数完成依赖收集this.target[this.key]}

在这里插入图片描述

它保存的是当前正在新建的wather实例。不一定要保留在Dep.target中,Dep.aaa,window.aaa都可以。也许现在你还不知道为什么要保存它。但是我保证,你在10行之内就知道它是干什么的了。
由于刚刚触发了响应式对象的get方法,我们回到响应式类Observe中。
在这里插入图片描述
上一小节最后一句
在这里插入图片描述
那说明响应式对象的每一个key都要有一个数组来存它全部的依赖。改写Observe类。

classObserve{constructor(data){
                Object.entries(data).forEach(item=>{// 新建依赖收集器const dep =newDep()
                    Object.defineProperty(data, item[0],{get(){// 劫持对象的get方法// 新建watcher实例会执行到key的get方法 所以Dep.target就是key需要收集的依赖if(Dep.target !==null){// 避免重复收集
                                dep.addDep(Dep.target)}return item[1]},set(newValue){// 劫持对象的set方法  // 这里要注意的是如果新值是对象类型 不要忘记响应化if(typeof newValue ==='object')observe(newValue)
                            item[1]= newValue
                            // 通知依赖更新
                            dep.noticeWatcher()}})})}}

写到这里也许你开始不耐烦了。但是

已经结束啦!!!!

是的你没有看错,但你完成这一步。Vue的响应式已经全部完成啦!。当然,我们实现的是简易版本的。但是请你相信。当你认真看完并理解。你已经掌握了Vue响应式的核心了。

出其不意的挂上全部代码

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><divid="app">
        {{name}}
    </div><script>classDep{constructor(){// 保存响应式数据的所有依赖,这里的依赖就是watcher实例this.depList =[]}// 由于是一对多的关系,所以要有一个add方法addDep(watcher){this.depList.push(watcher)}// 通知全部的watcher更新视图noticeWatcher(){// 上面已经说过了,每一个依赖都是一个watcher实例// noticeWatcher就说明watcher(订阅者)已经收到了通知// 需要更新domthis.depList.forEach(item=>{
                    item.upDateDom()})}}// observe 函数,observe中文是观察,观察一个普通对象 使它变成响应式的constobserve=(target)=>{// 如果不是对象数据类型,只是普通类型那么变成响应式if(typeof target !=='object')return// 新建一个Observe类 实现响应式的具体逻辑newObserve(target)}classObserve{constructor(data){
                Object.entries(data).forEach(item=>{// 新建依赖收集器const dep =newDep()
                    Object.defineProperty(data, item[0],{get(){// 劫持对象的get方法// 新建watcher实例会执行到key的get方法 所以Dep.target就是key需要收集的依赖if(Dep.target !==null){// 避免重复收集
                                dep.addDep(Dep.target)}return item[1]},set(newValue){// 劫持对象的set方法  // 这里要注意的是如果新值是对象类型 不要忘记响应化if(typeof newValue ==='object')observe(newValue)
                            item[1]= newValue
                            // 通知依赖更新
                            dep.noticeWatcher()}})})}}classVue{constructor({ el, data }){this.el = document.querySelector(el)this.data = data
                // 执行observe函数将普通对象转换成响应式对象observe(data)}}classWatcher{constructor(target, key, callBack){this.target = target
                this.key = key
                this.callBack = callBack
                this.getValue()// 依赖收集完成之后重置,避免重复收集
                Dep.target =null}// 传入的callback就是dom的更新函数,updateDom就是执行这个函数upDateDom(){this.callBack()}getValue(){// 保存当前watcher实例
                Dep.target =this// 执行getValue函数完成依赖收集this.target[this.key]}}var app =newVue({el:'#app',data:{name:'wxs'}})newWatcher(app.data,'name',()=>{
            console.log('假设这是一个dom更新函数!!!')})</script></body></html>

算上html结构以及换行才125行代码。学会它,超过隔壁莽村李青!


本文转载自: https://blog.csdn.net/qq_38280242/article/details/128982994
版权归原作者 星海拾遗 所有, 如有侵权,请联系我们删除。

“莽村李青都看得懂的Vue响应式原理”的评论:

还没有评论