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