vlambda博客
学习文章列表

Vue框架虚拟DOM和diff算法

一、diff算法的来由

我们都知道Vue是基于数据驱动的,数据变化之后,不会像jQuery那样进行页面全部节点的渲染,而是进行局部节点的渲染,那如何判断哪个节点变化呢?

利用的就是虚拟DOM和真实DOM的对比,而对比的方法就是diff算法。

二、虚拟DOM怎么回事(virtual DOM)

为了查看节点变化,利用JS把DOM元素变为一个对象,内部的结构和DOM数是一样的

DOM元素:

<div>
<h2>我是文本</h2>
</div>

虚拟DOM对象:

let oldVnode = {
tag: 'div',
children: [
{ tag: 'h2', text: '我是文本' }
]
};

如果DOM元素变化了,例如 子元素由h2标签变为 p标签了,

<div>
<p>我是文本</p>
</div>

let newVnode = {
tag: 'div',
children: [
{ tag: 'p', text: '我是文本' }
]
};

我们可以直接对比oldVnode和newVnode两个变量就可以了

三、diff对比的原则

对比原则1:逐层对比

第一层不一样,直接替换;第一层一样,进行第二层对比……

对比原则2:从两边向中间

新旧元素的子节点首尾两两对比,有相同的,则放到新元素对应的位置;不相同就向中间移动,再次对比

四、新旧元素对比分为四大类

第一类,第一层(最外层)都不一样,则直接替换

旧元素,最外层div
<div>
<p>我是文本</p>
</div>
新元素,最外层p
<p>
<p>我是文本</p>
</p>

这种情况,不用对比了,直接替换就行了,(PS:貌似最新算法更新了,最外层不一样,会找子节点相同的)

第二类,外层元素一样,旧元素没有子节点,新元素有子节点,则更新子节点

旧元素,最外层div,没有子节点,光杆司令
<div>
</div>
新元素,最外层也是div,子节点p,直接创建p节点
<div>
<p>我是文本</p>
</div>

第三类,旧元素有子节点,新元素没有子节点,则删除旧的子节点

旧元素,最外层div,子节点p
<div>
<p>我是文本</p>
</div>
新元素,最外层也是div,没有子节点,把旧元素子节点p删除
<div>
</div>

第四类,新旧元素都有子节点,按照对比原则2,首尾两两对比,向中间移动

旧元素,最外层div,子节点p、span、h3、a
<div>
<p></p>
<span></span>
<h3></h3>
<a></a>
</div>
新元素,最外层也是div,子节点p、a、div、h3、h5
<div>
<p></p>
<a></a>
<div></div>
<h3></h3>
<h5></h5>
</div>

一定要记住,diff算法只是对比的手段,本质的落脚点是新元素,说白了,就是根据新元素的子节点,按照一定的规则去旧元素的子节点中,查找有无相同的,有无复用的

五、diff算法过程

我们以这个例子演示一下diff算法过程

旧元素,最外层div,子节点p、span、h3、a
<div>
<p></p>
<span></span>
<h3></h3>
<a></a>
</div>
新元素,最外层也是div,子节点p、a、div、h3、h5
<div>
<p></p>
<a></a>
<div></div>
<h3></h3>
<h5></h5>
</div>

为了方便对比,把旧元素子节点添加前缀Old,新元素子节点添加前缀New,即

旧:Old_p、Old_span、Old_h3、Old_a

新:New_p、New_a、New_div、New_h3、New_h5

开始对比啦,记住,每轮都是新旧首尾两两对比,且落脚点是新元素的首尾

第一轮对比:

新首VS旧首:New_p和Old_p,结果一样都是p,所以新元素首子节点p不用新创建,可以复用旧元素的p节点

新首VS旧尾:New_p和Old_a,不一样,不过已经在旧元素中找到了相同的,即已经复用了

结论:新元素首节点,在旧元素的首尾节点中找到了相同的,就可以复用,不用新创建,不参与第二轮对比

新尾VS旧首:New_h5和Old_p,不一样,新元素尾子节点h5无法复用

新尾VS旧尾:New_h5和Old_a,不一样,新元素尾子节点h5无法复用

结论:新元素尾节点,在旧元素的首尾节点中找不到相同的,无法复用,等待第二轮对比

第二轮对比:

此时的新旧元素及子节点情况(New_p已经复用,不参与对比了)

旧:Old_span、Old_h3、Old_a

新:New_a、New_div、New_h3、New_h5

新首VS旧首:New_a和Old_span

新首VS旧尾:New_a和Old_a

结论:新元素首节点,在旧元素的首尾节点中找到了相同的,就可以复用,不用新创建,不参与第三轮对比

新尾VS旧首:New_h5和Old_span

新尾VS旧尾:New_h5和Old_a

结论:新元素尾节点,在旧元素的首尾节点中找不到相同的,无法复用,等待第三轮对比

第三轮对比:

此时的新旧元素及子节点情况(New_p、New_a已经复用,不参与对比了)

旧:Old_span、Old_h3

新:New_div、New_h3、New_h5

新首VS旧首:New_div和Old_span

新首VS旧尾:New_div和Old_h3

结论:新元素首节点,在旧元素的首尾节点中找不到相同的,无法复用,等待第四轮对比

新尾VS旧首:New_h5和Old_span

新尾VS旧尾:New_h5和Old_h3

结论:新元素尾节点,在旧元素的首尾节点中找不到相同的,无法复用,等待第四轮对比

注意,因为本轮没有匹配到可复用的节点,准备等待第四轮对比,但旧元素就剩下两个子节点了,下轮还是匹配不到,所以本轮结束后,就直接新创建 首节点New_div和尾节点New_h5

第四轮对比:

此时的新旧元素及子节点情况(New_p、New_a已经复用,New_div、New_h5已创建,都不参与对比)

旧:Old_span、Old_h3

新:New_h3

新元素就剩下New_h3了,直接对比就行了

New_h3和Old_span

New_h3和Old_h3

结论:在旧元素的首尾节点中找找到了相同的,可以复用,不用新创建

此时,新元素子节点已经全部OK,旧元素子节点就剩下Old_span,直接删除就OK了

至此,这个案例中的diff算法就完成了,其实还有一种情况需要说明,如果某轮4种对比都没有结果,就应该去旧元素子节点上寻找相同的key,这也是为什么Vue当中要求添加key的原因。

六、最后

关于diff算法过程实际比这个案例要复杂,考虑的情况也比较多,本文仅仅是个人学习的总结,如对各位有所帮助,那也就可以了。