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拿到下标,从而获取节点,不需要再进行循环。

Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐