![760bdb94a7cbb311ceda57a569059349.png](https://img-blog.csdnimg.cn/img_convert/760bdb94a7cbb311ceda57a569059349.png)
最近因为赶新项目,好久没跟新文章,请原谅,已经预备了很多主题,请期待~
今天的主题会围绕Vue的变化侦测展开探讨,会先简单的介绍MVVM的原理和实现一个基于“推”的变化侦测。
Agenda如下,
- MVVM,会简单的介绍下MVVM的原理,接着会针对以下两点进行着重探讨,
(1) 数据的双向绑定
(2) 变化侦测的两种类型,即“推PUSH”和“拉PULL”。
2. Reactive - Object / Array,模拟实现对一个Object的变化侦测,需要理解以下知识点,
(1) 如何追踪属性变化?
(2) 什么是依赖?
(3) 怎么通知依赖?
(4) 如何收集依赖?
除此之外,针对数组的变化侦测的实现,需要借用拦截器的原理。
3. Compile,模板解析器,即模拟Vue模板是如何被解析的,奔例会简单的解析 v-model 指令和 {{}}指令
4. Demo,Q & A
![75d580e047b166f0dbe81d000e44555e.png](https://img-blog.csdnimg.cn/img_convert/75d580e047b166f0dbe81d000e44555e.png)
NK1 - MVVM
![3a23df6d89c9dde553c587fb44f36563.png](https://img-blog.csdnimg.cn/img_convert/3a23df6d89c9dde553c587fb44f36563.png)
MVVM,全称Model-View-ViewModel,即模型-视图-视图模型。
MVVM模式最早是由微软公司提出的一种简化用户界面的时间驱动编程方式,强调分离前端开发与后端业务逻辑的耦合。
View层,视图、模板,负责将数据模型转化为UI。
Model层,模型、数据,泛指从后台获取数据并且能够操作数据。
其核心思想主要是ViewModel层,该层向左与视图进行数据的双向绑定,实现模板自动渲染,向右与Model层通过接口来获取数据或跟新数据。
![8284b55d3ec13123ff641572eb8b5f4e.png](https://img-blog.csdnimg.cn/img_convert/8284b55d3ec13123ff641572eb8b5f4e.png)
区别于MVC:Model层与View层耦合,交互形成一个回路,流程:用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)。
![37163bf68832c481fa294ffac42674ef.png](https://img-blog.csdnimg.cn/img_convert/37163bf68832c481fa294ffac42674ef.png)
区别于MVP:将Controller改名为Presenter,改变了通信方向,View和Model之间不再通信,而是交给Presenter来双向通信,与MVVM的区别是,Presenter需要手动渲染模板。
![0b0c12d173c3ea379e1819d0f40ea073.png](https://img-blog.csdnimg.cn/img_convert/0b0c12d173c3ea379e1819d0f40ea073.png)
用Vue的视角来解释MVVM的话,View视图对应的是Vue的模板和样式表;而ViewModel则是该组件的实例,每个组件的作用域都是独立的,互不影响;而Vue的Model层,在没有引入第三方状态管理的情况下,就是指组件中data属性中的内容。如果系统中引入了全局的Model层,比如VUEX,那Model层也包含一个和Vue组件脱离的对象。
![923c7cfafea2d1667645567d8e671b55.png](https://img-blog.csdnimg.cn/img_convert/923c7cfafea2d1667645567d8e671b55.png)
所以ViewModel的核心原理是来生成和维护视图数据层,而其优点主要体现在数据双向绑定,目前主要流行的响应式框架代表由Angular和Vue,两个又有不同的特点如下,
Angular采用脏数据检查的方式来遍历所有的Watcher,比较数据差异。
Vue采用数据劫持的方式,借用第三方API来劫持数据的每个属性对应的getter和setter方式,然后通知相关依赖,并且Vue2.x引入虚拟DOM来让节点更新优化。
![9a49651f06c6325ef4cb77f7b955aa1b.png](https://img-blog.csdnimg.cn/img_convert/9a49651f06c6325ef4cb77f7b955aa1b.png)
NK2 - 变化侦测
什么是变化侦测?
在理解变化侦测原理时,先来了解一个小知识点:渲染,
UI的渲染是指从数据状态生成DOM,接着输出到用户界面显示。
![f7286fb672cec9e3a65d7cc50d51eedb.png](https://img-blog.csdnimg.cn/img_convert/f7286fb672cec9e3a65d7cc50d51eedb.png)
我们知道Vue的渲染是声明式的,它在渲染过程中,用Vue模板来映射数据状态跟DOM的关系。
![db7ee5a3884acddcc6f2d3fd1d567ebd.png](https://img-blog.csdnimg.cn/img_convert/db7ee5a3884acddcc6f2d3fd1d567ebd.png)
当状态在更改的时候,我们的用户界面也会跟着一起刷新,但其会出现一个贫瘠,即当你修改的越频繁,导致UI可能会出现跟不上响应的情况,即卡顿。
所以这时候就酝酿出很多出色的前端模板框架,其中一个原理是如何处理UI和数据状态的映射绑定,接着是如何优化UI渲染问题,我们把该原理称之为变化侦测。
变化侦测的作用是侦测数据的变化,即当数据发生改变的时候,会通知视图进行相应的跟新。
NK3 - 变化侦测类型
变化侦测分为两种类型,一种是“推”PUSH,另一种是拉“PULL”,区别如下,
PULL的响应式框架代表有Angular和React,它们的特点主要是:不知道哪个状态变化了,当我收到信号后,就要进行差异“比较”。
Angular采取的是数据层级别的脏检查,而React是侦测View层的DOM节点比较。
![cbfa2f1ad3e018fbc4681967ea952e23.png](https://img-blog.csdnimg.cn/img_convert/cbfa2f1ad3e018fbc4681967ea952e23.png)
而PUSH的响应式框架代表为Vue,知道哪个状态变化,可以进行细粒度的更新。
![bb5c28369595c04262b7c3bcec144771.png](https://img-blog.csdnimg.cn/img_convert/bb5c28369595c04262b7c3bcec144771.png)
NK4 - 实现变化侦测步骤
网上有很多种方式去实现一个变化侦测,但是只要掌握其核心命脉就可以贯穿其原理,其实现步骤,即
- 实现一个Compile
- 实现一个Observer
- 实现一个Watcher
- 实现一个Dep
Compile,指的是指令解析器,即扫描和解析每一个元素DOM节点元素上绑定的指令,然后根据指令模板替换数据,以及绑定相应的跟新函数,本文中只进行指令 v-model和{{}}指令的解析。
Observer,网上称为“数据监听器”,能够对数据对象进行监听,即当读取或者修改数据属性的时候,能够追踪到变化。
Watcher,网上称为“订阅者”,简单来说,它就是一个依赖,能够监听到其绑定的数据属性发生变化(读取/修改),然后做出改变,所以它通常会是一个模板指令,computed属性等, 后续会介绍。
Dep,网上称为“消息订阅器”,简单来说,它就是一个收集器,它主要负责(1)用来收集依赖(2)当数据属性发生变化时,来通知相关依赖
![12ae6ffae072d71f25b594c1b7109587.png](https://img-blog.csdnimg.cn/img_convert/12ae6ffae072d71f25b594c1b7109587.png)
NK5 - Reactive - Object / Array
![e81d807c9c8893a262857aaa29d85e9a.png](https://img-blog.csdnimg.cn/img_convert/e81d807c9c8893a262857aaa29d85e9a.png)
即模拟实现对一个Object的变化侦测,其效果Demo如下。
![87d3f7218e38c043b4b124fff811669c.gif](https://img-blog.csdnimg.cn/img_convert/87d3f7218e38c043b4b124fff811669c.gif)
根据上一页主题,我们根据实现步骤先来画个流程图,
(1)实现一个Observer,让数据对象变得可“观察”
假设我们要监听的Data数据格式如下,
const
首先我们需要将Data属性("name"和"age")通过Oberver转换成Getter/Setter的形式来追踪变化,即
![4a6b2dfcc25b7c48eedde8a7dad31837.png](https://img-blog.csdnimg.cn/img_convert/4a6b2dfcc25b7c48eedde8a7dad31837.png)
(2)实现一个Watcher, 依赖即订阅者,能够订阅并收到每个属性变动的通知
假设我们的依赖是模板上的一个指令,如下
<
当外界(模板指令)通过Watcher来读取数据时,会触发依赖所绑定的属性("name")Getter,从而我们可以将Watcher添加到依赖收集器中。
![f96052f49474e044cd1d17c6d044485b.png](https://img-blog.csdnimg.cn/img_convert/f96052f49474e044cd1d17c6d044485b.png)
(3)实现一个Dep,依赖收集器即消息订阅器,用来收集依赖和管理依赖(通知)
上面已经引入了Dep收集依赖的时机,即当Watcher被外界引入的时候。
接着当我们有一个可以改变数据属性的时机,如
<
当数据发生变化时,会触发数据属性的Setter,从而Dep会为绑定的依赖发送通知,Watcher接收到通知后,会向外界发送通知,变化通知外界触发视图跟新
![58dbe5216c55d68a310eadefa605e50b.png](https://img-blog.csdnimg.cn/img_convert/58dbe5216c55d68a310eadefa605e50b.png)
当掌握完以上基础原理之后,我们做一个简单的变化侦测实现,参考了一个前端工程师的文章,对其进行了小修改,加深认识。
NK6 - 变化侦测实现 - Observer
实现一个Obsever
![980433baa370dac5dcd7f58ebe0cc004.png](https://img-blog.csdnimg.cn/img_convert/980433baa370dac5dcd7f58ebe0cc004.png)
如何追踪数据属性变化?针对该问题,我们需要借用第三方API:Object.defineProperty,其原理如下,能够控制对象属性的Getter和Setter权限,缺点是当一个对象后续新增或删除属性是,是无法被追踪。
![4d9b09381b75e188d59dc257ecb76993.png](https://img-blog.csdnimg.cn/img_convert/4d9b09381b75e188d59dc257ecb76993.png)
// 1.如何追踪依赖变化
其Console输出结果如下
![3c24962ae32b94fe4b3d527befdda1b9.png](https://img-blog.csdnimg.cn/img_convert/3c24962ae32b94fe4b3d527befdda1b9.png)
优化 - 递归所有的Key:
const
其Console的输出结果,
![49b451134b64888a0b633b50ab95f434.png](https://img-blog.csdnimg.cn/img_convert/49b451134b64888a0b633b50ab95f434.png)
NK7 - 变化侦测实现 - Watcher
什么是Watcher?
![b0fd684b4b7cbab7bf3a7375767e32d1.png](https://img-blog.csdnimg.cn/img_convert/b0fd684b4b7cbab7bf3a7375767e32d1.png)
网上很多种别称,但本文中,它指的就是依赖,它主要的功能是当对象属性发生变化(读取或者修改)时,所要通知的目标,而依赖有很多种形式,例如可能是模板上绑定的指令,
![e6db8ed29ed1649d2b67254f8d0d8d27.png](https://img-blog.csdnimg.cn/img_convert/e6db8ed29ed1649d2b67254f8d0d8d27.png)
在Vue中依赖还可能是computed属性,warch或者props等
假设,我们定义一个computed属性type,它的逻辑需要根据obj.age,来判断他是成年人还是未成年人。
// 实现计算属性的监听
接着我们再来实现一个计算属性的变化侦测,
// 当计算属性的值被跟新时调用
onComputedUpdate主要的作用是,当计算属性变化的时候,我们需要去通知依赖改变。
当我们调用的时候以下命令时,
console
其Console的输出结果,
![ee4fa88a3f618791888bd23051283c5d.png](https://img-blog.csdnimg.cn/img_convert/ee4fa88a3f618791888bd23051283c5d.png)
能够正常刷新type的值,但其特点需要手动才能查看type的值,但是这不是我们想要的,我们想要的是改变属性的时候,能主动通知依赖,所以问题来了,
如何主要通知?
我们知道,当一个可观测对象的属性被读写时,会触发它的getter/setter方法。换个思路,如果我们可以在可观测对象的getter/setter里面,去执行监听器里面的onComputedUpdate()方法,是不是就能够实现让对象主动发出通知的功能呢?
NK8 - 变化侦测实现 - Dep
实现一个Dep,它的主要功能有三点
![04eeef82272f973a686480701eac9d4e.png](https://img-blog.csdnimg.cn/img_convert/04eeef82272f973a686480701eac9d4e.png)
(1)创建一个“依赖收集器”,专门用来存储依赖用的
![2142b426ee7063046e5203180ab7b831.png](https://img-blog.csdnimg.cn/img_convert/2142b426ee7063046e5203180ab7b831.png)
它只有一个属性,即"target"属性指向的是依赖,即Watcher实例
(2)修改上面Watcher中绑定要触发的回调函数,但此时的Dep.target就是该Warcher("type")
![1be8d61726295ac93c8c2e54553f68ef.png](https://img-blog.csdnimg.cn/img_convert/1be8d61726295ac93c8c2e54553f68ef.png)
(3)修改Observer中注入的Getter和Setter,
getter时,收集依赖
setter时,触发依赖
![3a4502c94478d4a9dc7ef9188070c6d8.png](https://img-blog.csdnimg.cn/img_convert/3a4502c94478d4a9dc7ef9188070c6d8.png)
其Console的结果显示如下,
![1b18b5f52343e690abe62e3ee62c29ce.png](https://img-blog.csdnimg.cn/img_convert/1b18b5f52343e690abe62e3ee62c29ce.png)
以上就完成了一个变化侦测的具体实现方式,而针对数组的变化侦测会稍微麻烦一点,因为数组的变化侦测是针对它原型的方法,如"push()","pop()","shift()","unshift()","splice()", "sort()","reverse()"。
所以要考虑的点是,如果直接把变化侦测注入在原型方法上,则会影响到所有的数组,所以针对数组的变化侦测,我们需要加多一个类似拦截器的家伙来拦截访问数组的方法。
其特点还要考虑兼容性,如果浏览器提供属性 “_proto__”可以访问到原型,我们就可以在此挂载拦截器,否则,我们只能直接挂载到被侦测的数组上,当作对象方法来用。
![046d173c085e4875112181589c50ab79.png](https://img-blog.csdnimg.cn/img_convert/046d173c085e4875112181589c50ab79.png)
其原理逻辑如下图,
![61668d8a826ba4bc22cbc2514b1a17fb.png](https://img-blog.csdnimg.cn/img_convert/61668d8a826ba4bc22cbc2514b1a17fb.png)
NK9 - Compile指令解析器
![d9c67e7441e42ae85f076e9e7709625e.png](https://img-blog.csdnimg.cn/img_convert/d9c67e7441e42ae85f076e9e7709625e.png)
指令解析器,对每个元素节点的指令进行扫描和解析,例如解析本文中 v-model 指令和 {{}} 指令
这里不深究,网上有很多案例和优化,但其工作原理主要是,解析指令,初始化模板数据,初始化Watcher,即进行依赖收集。
![ff286200f63c117de28f10ac25ccad15.png](https://img-blog.csdnimg.cn/img_convert/ff286200f63c117de28f10ac25ccad15.png)
而代码显示如下
![c26b30dd7d4b116af9c25cd526f4b828.png](https://img-blog.csdnimg.cn/img_convert/c26b30dd7d4b116af9c25cd526f4b828.png)
NK10 - Demo(Q & A)
![5803abcf35e45d3b78b9ed64d078f35f.png](https://img-blog.csdnimg.cn/img_convert/5803abcf35e45d3b78b9ed64d078f35f.png)
参考文章
(1)深入浅出基于“依赖收集”的响应式原理
(2)vue的双向绑定原理及实现
(3)Vue变化侦测 - Berwin