手写实现vue的MVVM响应式原理

Posted at867604340

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写实现vue的MVVM响应式原理相关的知识,希望对你有一定的参考价值。

  MVVM响应式实现原理:

1.模板编译

2.数据劫持

3.watcher

 

文中应用到的数据名词:

MVVM   ------------------        视图-----模型----视图模型                三者与 Vue 的对应:view 对应 templatevm 对应 new Vue({…})model 对应 data

nodeType       判断节点是否是元素节点

querySelector    创建一个元素节点 

createDocumentFragment     文档碎片

attributes      获取元素属性集合

textContent   获取文本内容

reduce( prev,next,currentIndex){}               一个可以用上一个元素和当前元素做处理的方法

defineProperty(obj,key,value){}                数据拦截的主要方法

 

技术图片

技术图片

 技术图片

 

首先建立一个vue的实例,建立mvvm.js ,构建 mvvm类。  获取el的节点 和 data   放入实例中,在将Observer.js(数据劫持)和Compile.js(模板编译) 放入mvvm的js   ,全部在index页面运行. 

第一步:模板编译

我首先制作Compile.js ,也就是模板编译  。

技术图片

 

首先需要获取el 这个属性的值   用nodeType === 1判断是不是元素节点. 如果不是则用 queryselector() 生成一个节点 。  这样做的目的是,有些人el:#app  有些人是document.getElementById(‘app‘)。 不管俩者如何,我们都要生成一个节点来供后续使用。

技术图片

 

 随后判断el节点是否存在,如果存在。则进行编译 ,  这里编译最好不要在dom里进行遍历编译,非常耗性能 。 我推荐的是用 createDocumentFragment() 方法. 建立一个虚拟节点对象, 在这个虚拟节点对象里进行遍历以及对应的操作。

技术图片

 

那么说到虚拟节点, 我们需要将我们获取的el节点整个放入进去 ,进行遍历,将app里的每一个子节点都搬到fragement 变量中。

技术图片

 

 

 然后进行节点的编译。这里的节点又分为元素节点和文本节点。 还是用刚刚的nodeType判断区分吗,然后做对应的操作。

技术图片

 

 接下来我们先编译元素节点  首先我们需要知道,获取元素节点要做什么,为什么获取元素节点。 我是希望通过获取元素节点上的关于vue的指令,比如:v-model,v-html,v-for 。等等...   那么这些指令是放在元素节点上的属性里,所以我们用 attributes 获取元素节点的属性名的集合 ,也就是我们说的v-model 。通过遍历这个attr属性名的集合,获取每个属性名。通过isDirective函数判断attrName包含 v-  的属性,这里我做给假设,好方便理解。 这里通过上面的过滤,可以得出attrName 是一个指令名。那我假设这个指令名为v-model。 我首先获取v-model的值,也就是expr。然后做一个解耦对象CompileUtil ,方便后面制作其他的指令。所以这里调用的是CompileUtil[model]{node,this.v,,expr};

 

技术图片 技术图片

 

 

 

 

调用model的指令后,在model这个函数里做相对应的处理。这里的watcher构造函数先不用管,后面的事情。 这里的uptate[‘modelUptate‘]和model一样放在CompileUtil 中,方便管理。 如果updateFn存在的话,则执行updateFn(),将v-model的值赋予input节点的value.下图中的getVal 是防止 v-model=’messge.a‘ 这种嵌套对象的。这个函数里,首先利用split将messge.a 拆分成[messge,a] 数组。然后利用reduce方法   放回 上一个元素[当前元素],而最下面的vm.$data 是reduce方法遍历的初始值。也就是 data 。

因为data:{ messge:{ a:‘hello.world‘  }   }.这样的编译,元素节点就可以编译出来了,可以将data的值编译到元素节点上了。

  

 

技术图片技术图片技术图片

 

接下来编译文本节点,那文本节点,我们首先获取文本节点里的值,然后利用正则的test找{{ a }} , 和之前的元素节点一样,执行对应的函数。,执行对应的行数。这里第86-90 可以先不管,不过这里的textVal和上面的getVal 函数不一样,首先是需要将符合条件的元素里的变量取出来  也就是 {{   a  }}里的a ,argments[1] 就是a变量  。 在考虑到对象嵌套,就执行上面的getVal。然后就可以将data里的值替换到文本里了。

 

技术图片技术图片技术图片技术图片

 

 

 

 这样元素节点和文本节点都编译完成了。然后将整个虚拟节点丢回dom树里去 。MVVM的编译就结束了

技术图片

 

 

 

 第二步:数据劫持,函数很少。但比较绕.这里执行observe,利用递归遍历,将data里的键值对全部拿出来处理,执行defineReactive函数,这里18行可以先不看。 看下面的最重点的Object.defineProperty()。这里要传入劫持的对象,劫持的键,以及回调函数。这里回调函数里俩个参数在下图。

然后,get函数是取值是做对应的操作,set函数是设置值做对应的操作。至此数据劫持就完成了

技术图片

 

 

技术图片

 

 

 第三步:watcher 监察者 ,一旦变化执行对应的操作。也就是将模板编译和数据劫持俩个函数联系在一起。有衔接。

这里创建watcher类,将需要的参数获取。 vm是实例,expr是值,cb是回调函数callback。watcher实例里的value = get方法的返回值,value执行一次嵌套处理返回。这里监察者作用主要是 一 更新值,二是执行callback回调函数cb。三将自己的实例,放入dep的target里。那么watcher监察者就制作好了。

技术图片

 

 最后的连接部分,首先data里的每个属性值都被加上了set和get

获取值

在最开始编译的时候,编译节点的文本节点处理和元素节点处理的时候执行watcher函数,在watcher函数里的get函数中将 watcher函数自己放入dep的target中。然后也在访问值的时候,则会执行get函数,将 每个watcher放入dep数组中 。技术图片

 

 

 

技术图片

 

 修改值

 在修改值的时候,会触发Observer.js 的defineProperty的set函数,set函数里比较新的值和旧的值,value是编译时候的值,newValue是set函数的第一个参数,也就是修改后的新值 。   将俩者比较,如果不同,就执行Dep构造函数的notify函数。notify则会遍历全部存在的dep数组里的watcher的update方法。在watcher的update方法中,比较值的不同,如果不同就则执行回调函数,将视图更新。这个回调函数是嵌套在处理文本节点和元素节点的方法里。

 

 

v-model的双向绑定

 

 

 至于v-model的双向绑定,其实是绑定输入框的输入事件。将输入事件新的值赋值给input节点的value值,然后值的改变,执行set函数,将视图改变。视图的改变,会执行wacther的回调函数,文本节点也会重新赋值。

 

 

 

 

这就是mvvm响应式原理的实现,如果有残缺讲不清楚的地方,欢迎指出。谢谢。

 

以上是关于手写实现vue的MVVM响应式原理的主要内容,如果未能解决你的问题,请参考以下文章

能不能手写Vue响应式?前端面试进阶

手写 Vue3 响应式系统:核心就一个数据结构

能说说vue的响应式原理吗?

【手把手教你搓Vue响应式原理】(一)初识Vue响应式

深入 Vue 响应式原理,活捉一个 MVVM

vue3.0的proxy响应式原理简单实现