1.虚拟DOM的概念
虚拟DOM的概念是通过状态生成一个虚拟节点树,然后使用虚拟节点树进行渲染。在渲染之前,会使用新生成的虚拟节点和上一次生成的虚拟节点进行对比,只渲染不同的部分
2.vue中的虚拟DOM
vue中状态变化时,只能通知到组件,组件内部的变化需要通过虚拟DOM去进行比对与渲染
在vue中,我们使用模板来描述状态与DOM之间的映射关系。vue通过编译将模板转换成渲染函数,执行渲染函数就可以得到一个虚拟节点树,使用虚拟节点数就可以渲染页面
虚拟DOM在vue中主要提供与真实节点对应的虚拟节点vnode,然后需要将vnode和oldVnode进行比对,然后更新视图,对比两个虚拟节点的算法是patch算法
3.VNode
在vue中存在一个VNode类,使用它可以实例化不同类型的vnode实例,而不同类型的vnode实例各自代表不同类型的DOM元素,vnode的作用主要是描述了怎样去创建DOM节点
vnode的几种类型
1.注释节点
2.文本节点
3.元素节点
4.组件节点
5.函数式组件
6.克隆节点
事实上,只有前三类节点会被创建并插入到DOM中
vnode有tag属性就是元素节点
没有tag属性就可以看isCommet属性,true代表注释节点,false就是文本节点了
各自的创建方法
元素 createElement
文本 createTextNode
注释 createCommet
通过appendChild方法插入到父元素下
删除的方法需要进行封装,原生的删除方法是removeNode,封装主要为了适应不同的平台,是删除操作在各个平台上都能正常的运行
4.patch算法
patch算法的目的是在现有的DOM上修改来更新视图,这样做主要是为了获得更好的性能
patch有三种情况,增加节点,删除节点,修改节点
1.新增节点,当oldVnode中不存在而在vnode中存在时,说明该节点时新增节点,使用vnode生成真实的DOM元素插入到视图中
2.删除节点,当oldVnode中存在而在vnode中不存在时,该节点为废弃节点,使用vnode创建一个新的节点,插入到旧节点的旁边,然后删除旧节点实现替换
3.更新节点,当vnode与oldVnode进行对比,发现是同一个节点,然后进行更详细的对比,修改对应的真实DOM
更新节点的具体方法如下:
1.如果是静态节点直接跳过,因为静态节点一旦渲染就不会再变化
2.根据新节点是否有text属性,更新节点可以分为两种不同的情况
如果新生成的节点有text属性,那么不论之前的子节点是什么,直接调用setTextContent方法来将视图中DOM节点的内容改为虚拟节点的text属性所保存的文字
如果新生成的节点没有文本属性,那么它是一个元素节点,元素节点通常会有子节点,也就是有children属性
如果新旧节点都有children属性,就需要对children进行一个更详细的对比和更新
如果新节点有children属性,而旧节点没有children属性,那么需要把新vnode的children一个一个的创建插入父元素下
当然也有可能新节点没有children,那就直接把children属性都删掉就行了
5.更新子节点的具体方法
1.更新节点
2.新增节点
3.删除节点
4.移动节点
1.新增节点
新旧两个子节点列表通过循环进行对比,所以创建节点的操作是在循环体内执行的,其具体实现是在oldChildren中寻找本次循环所指向的新子节点相同的节点,如果在oldChildren中没有找到本次循环所指向的新子节点,那么说明本次循环所指向的新子节点是一个新增节点。对于新增节点,我们需要执行创建节点的操作,并将新创建的节点插入到oldChildren中所有未处理的节点的前面
2.更新子节点
更新节点本质上是一个当一个节点同时存在于newChildren与oldChildren中需要执行的操作并且子节点的位置相同
3.移动子节点
移动子节点是newChildren和oldChildren中某一个节点是同一个节点,但是位置不同,所以在真实的DOM中需要将这个节点的位置以新虚拟节点的位置为基准进行移动
Node.insertBefore进行节点的插入,找到移动节点的位置,从左到右循环newChildren,每次循环newChildern都在OldChildren中寻找与这个节点相同的节点进行处理,也就是说,newChildren中当前被循环到的节点的左边都是被处理过的,所以移动的位置就是所有未处理节点的最前面
4.删除子节点
删除子节点,本质上是删除那些oldChildren中存在但newChildren中不存在的节点
5.优化策略
通常情况下,并不是所有子节点的位置都会发生变化,一个列表中总有几个节点的位置是不变的。针对这些位置不变的或者说位置可以预测的节点,我们不需要循环来查找,因为我们有一个更快捷的查找方式。假设有一个场景,我们只是修改了列表中每个数据的内容,而没有新增或删除数据等,这种情况下newChildren和OldChildren中所有节点的位置都是相同的。所以为了提高性能,我们只需要尝试使用相同位置的两个节点来对比是否是同一节点,如果是同一节点,直接更新,如果尝试失败,再循环列表
新前与旧前是同一个节点或者新后与旧后是同一个节点,那么直接更新即可
如果是新前与旧后,除了更新真实的DOM节点之外,还需要把节点移动到oldChildren中所有未处理节点的最前面
同理,如果是新后与旧前,除了更新真实的DOM节点之外,还需要把节点移动到oldChildren中所有未处理节点的最后面
标记处理过的节点
oldStartIdx, oldEndIdx, newStartIdx,newEndIdx
while(oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
}
这样的循环可以保证不会保证没有重复的节点,当新旧子节点列表不同是,进行删除或者是添加的操作即可,当oldChildren的节点数更多的时候,说明删除了一些节点,newChildren更多的话,就是新增节点,直接添加到DOM中
6.vue中设置key
key与index有对应的关系,就生成了一个key对应着一个节点下标,那么在进行新旧节点的对比时,可以直接通过key拿到下标,从而获取节点,不需要再进行循环。