再次看了上次写的博客关于Vue的MVVM,发现虽然介绍了MVVM的原理,但是感觉还不够详细,现在就再次根据这篇博客写详细一点,来看看new Vue
的时候Vue究竟做了些什么事。
我想,以需求作为出发点来理解原理会比较容易,所以这篇博客会以提出需求 -> 解决需求的方式来写。
Vue中的MVVM原理介绍
可以先阅读我的这篇博客了解一下关于Vue的MVVM,另外需要记住这一幅图(很重要),这张图就是本篇博客的概括:
需求提出
首先我们来看Vue最最基础的用法--在id为app的元素上显示出数据msg的内容,平时都是Vue(Vue技术栈的话)在做这件事,那么给我们自己如何实现呢?
-
需求
image.png -
达成效果
image.png - 总结:
这一步是model(数据模型) -> view(视图)的绑定
需求分析
- ①:首先我们要根据el获取得到当前元素;
- ②:过滤出其中符合mustache语法
{{...}}
中的字符; - ③:根据②拿到的字符取到data对象中相应的数据并对对应的整个
{{..}}
字符进行替换;
创建MVVM类
-
创建MVVM类进行初始化
image.png
我们给自己写的Vue命名为MVVM,创建一个MVVM的类,然后获取其中的el
和data
;
image.png -
但是这时候会发现,如果需要拿到
image.pngdata
中的数据需要通过this.data.msg
才能拿到,而平时用Vue时在实例中只需要this.msg
就能拿到,所以就出现一个问题:如何才能msg
放到当前的vm实例中呢?
-
将data中的属性代理到当前vm实例中
image.png
答案是用代理的方式,这里就涉及到一个属性Object.defineProperty(该属性是Vue的核心,不了解的可要看一下哦),代码如下:
image.png -
总结:这一步就是
new MVVM
时做的一部分初始化工作,下一步是获取el
的节点,并进行编译工作
节点编译器(Compiler)
-
创建Compiler类
image.png
这个类的职能是获取data
中的数据,并对节点中的{{...}}
进行替换,所以需要传入的参数有el
和当前的vm实例;
image.png -
创建节点副本
image.png
如果直接操控DOM元素,所需要的性能花销较大,所以在Vue中采用了假节点(createDocumentFragment),通过更新假节点然后替换当前节点的方式。
注意:在这一块中,创建出来的假节点fragment
对应的是el
节点,所以方法是将el
节点内的所有子节点都扔进fragment中
,如下代码:
-
对节点副本进行编译
-
因为
image.pngfragment
跟随的是el
节点,所以需要考虑一个情况:fragment
的子节点中有文本节点和元素节点两种,这时候就需要分情况进行编译了,这里可以通过[node.nodeType] -
文本节点编译
image.png
文字节点的编译又需要考虑多种情况:
①:{{...}}
前面是否有普通文本msg{{msg}}
;
②:{{...}}
后面是否有普通文本{{msg}}msg
;
③:{{..}}
里面有可能是某个对象中的值{{message.msg}}
;
这时候我们把情况修改得复杂一些,包含上面所有的情况:
-
设想:
image.png
如何解决问题①和问题②:创建一个数组,然后将截取第一个{{...}}
文本之前的普通文本,作为普通文本放进数组中,然后再截取一个{{...}}
文本作为tag文本放进数组中,以此类推对整个文本进行分割;
所以我们需要做的有1.创建一个数组用于容纳分割后的文本textLIst
;2.创建一个能过滤出{{...}}
文本的正则;3.分割出{{...}}
文本中的键:比如{{msg}} 分割出 msg
;5.创建用于定位{{...}}
左后一个花括号所在index的坐标lastIndex,初始值为0;6. 事先预备一个match作为备用的正则匹配项;如下:
-
解决问题①:
image.png
考虑到文本中可能有多个{{...}}
文本的存在,并且还需要知道{{...}}
所处的index,所以使用RegExp.prototype.exec就可以直接得到{{...}}
里的键名,index的值,然后赋值给match,之后套入到一个while
循环中执行,千万不能写成这样,会导致死循环,原因在上面exec
的mdn文档中有解释:
需要这样写:
image.png
之后有几个{{...}}
文本,while
循环就会执行几次,得出match的值:
image.png
如上图,因为已经拥有了index,所以我们现在可以将{{...}}
前的文字拿出来放进textList
中:
image.png
然后将{{...}}里的键名作为tag传入textList
中并将lastIndex
置为该{{...}}
文本之后:
image.png
这时候打印textList
发现从最后一个{{...}}
文本到文本开头的的所有文本都已经做好了分类,并且lastIndex
也已经为最后一个{{...}}
的最后一个花括号所在的index了:
image.png
剩下的就是将剩余文本也作为普通文本放入textList
中,因为上面的lastIndex的位置,所以我们直接通过lastIndex
和文本的长度判断可知最后面是否有文本需要进行compile:
image.png
image.png
注意这一步不能放在while循环中做,否则会导致重复的文本放入。
image.png -
我们将上面的步骤抽离出来单独作为一个函数
image.pngcompileText
,并将textList
返回出去;
到了这一步实际上我们已经拿到了文本的分类片段,是{{...}}
的文本则为tag,否则为普通文本,下一步就是进行文本的替换了 -
进行文本替换
image.png
这一步中我们的主要工作是对textNode
文本节点中的文本进行替换,那么需要做的步骤如下:
1 .拿到文本节点的父节点并创建一个用于替换的假节点;
2 .遍历textList
中进行过分类的文本片段,然后进行判断,如果非tag文本则据此创建一个文本节点并放进假节点中。
image.png
3 .如果是tag文本则在data
中进行取值,但是这时候要考虑一个问题了,文本中的{{XXX}}
实际上是一个v-text="XXX"
指令,Vue中还有很多指令,例如v-model
,v-for
等,他们都需要在data
中进行取值绑定等操作,这样的话就需要一个专门用于依据类型进行取值,绑定等工作的指令集合directives
,并且当前directives
里面需要一个专门处理v-text
的方法、一个专门用于绑定的bind
方法以及一个专门用于取值的getVMData
方法,然后还需要一个专门根据指令类型用于绑定后更新视图的集合updater
,里面同样需要一个text
方法:
image.png
4 .之后我们在当文本类型为tag时创建一个空的文本节点el
, 然后思考text
指令所承载的功能,并传入el
、当前vm实例、tag文本的值,并标明类型为text:
image.png
image.png
5 .解决问题③,在对tag进行绑定的过程中,免不了要先去获取到tag文本在data
中对应的值,这时候就需要考虑问题③中{{message.name}}
这样的情况了,可以使用字符串方法split
基于.
分割成的数组获取到在data
中正确的值:
image.png
-
-
- 现在我们已经拿到了
data
中对应tag的值newVal,并且也有了相对应的节点,那么就执行更新器updater
中对应类型的更新方法就可以了,在这里,就是更新相对应节点的文本内容textContent
,
image.png - 最后回到
compileTextNode
函数中,将compile好的文本节点放进假节点中,再将textNode
父节点中的文本替换即可
image.png - 这时候compile文本节点的工作就已经做完了,将处理后的
fragment
插入到真实节点el
中就可以看到效果了:
image.png
image.png -
但是此时还没有针对普通节点进行compile,所以如下html无法正常显示,下一步就是对节点进行compile:
image.png
image.png
对节点进行compile(进入compileNodeElement
函数)
由上图的html可以知道,针对节点进行的compile需要分为两类:
- 普通节点的compile,也就是节点内只有文本,例如
<div>msg{{msg}}msg{{message.name}}8888888</div>
; - 带有指令的的节点,例如:
<input type="text" v-model="msg">
-
对普通节点compile
对普通节点进行compile很简单,因为已经有了针对文本节点的compile,那么只需要创建一个通道,让普通节点进入文本节点的compile即可:
image.png
结果:
image.png -
带有指令的节点的compile(这里只针对
v-model
指令进行)
对带有指令的节点的节点进行compile需要做如下几件事:-
获取节点的所有属性名字,然后遍历,判断是否存在指令:
image.png
-
- 如果存在指令,就获取该指令的值和指令类型,例如
v-model=msg
就获取model
和msg
image.png
- 如果存在指令,就获取该指令的值和指令类型,例如
- 在指令集
directive
和更新器updater
中添加相应方法,这里是model
方法,并进行处理
image.png
image.png
- 在指令集
-
结果:
image.png
image.png
-
总结
该篇博客只对Vue中的初始化渲染原理做了介绍,也只是完成了流程图中Compile
的大部分model -> view
的绑定,但还未达成双向绑定,因此对数据的修改并不能对视图进行改变,这就是下一篇博客view -> model
的绑定所要介绍的:
网友评论