什么是Diff算法
众所周知,现如今使用Virtual DOM渲染为真实DOM的方法以及普遍用于我们的代码编程之中。在使用Virtual DOM渲染真实DOM时,不能全量的将整个Virtual DOM进行渲染,而只是会渲染改变了的部分,这就使用到了我们的Diff算法。
Diff算法,可以帮助我们减少页面的更新量,只更新更改的部分,实现最小差异化。
DOM树
DOM树是由HTML解析器构造的。DOM本身就是一个文档对象模型,HTML是document文档的根,当页面渲染加载到浏览器上时,DOM会将网页的文档转换成文档对象,可以对页面中的内容进行处理。
DOM树就是以html为根节点,一层一层往下伸展,CSS也会通过CSS解析器绑定样式到DOM树上对应的位置。
虚拟DOM树
由于JS执行速度与真实的DOM执行的速度有差异,在DOM操作的时候,可能会引起浏览器的回流和重绘,甚至由于适配的参数问题可能会引发性能问题。
所以,就出现了虚拟DOM。(虚拟DOM是被渲染函数h函数产生的)
虚拟DOM是利用js对象来模拟DOM节点,在发生真实的DOM树的改变之前,会先对虚拟DOM树进行操作,且只会更改改变了的部分。
snabbdom
snabbdom是一个著名的虚拟DOM库,是Diff算法的鼻祖,而我们经常使用的Vue的源码中就是借鉴了snabbdom。
Diff的执行过程
要了解Diff的执行过程,需要了解一下虚拟DOM树中的新旧节点。
新旧节点
虚拟DOM是由渲染函数h函数产生的,h函数用来产生虚拟节点(Vnode)。
Vue在DOM更新的时候,比较的就是Vnode节点,而新旧节点就是Vnode节点,由于Vnode节点是一个JS对象,所以在比较之后的结果不需要做过大的改动。
虚拟节点具备的一些属性
{
children: xx, // 子元素
data: {}, // 属性、样式
elm: xxx, // 虚拟DOM对应的真正DOM节点
key: xxxx, // 唯一标识
sel: 'div', // 选择器 select选择器
text: 'xxxxx', // 文本
}
虚拟节点上树
import { init } from 'snabbdom/init';
import { classModule } from 'snabbdom/modules/class';
import { propsModule } from 'snabbdom/modules/props';
import { styleModule } from 'snabbdom/modules/style';
import { eventListenersModule } from 'snabbdom/modules/eventlisteners';
import { h } from 'snabbdom/h';
// 创建patch函数 ===> patch函数服务于将虚拟节点上树
const patch = init([classModule, propsModule, styleModule, eventlistenersModule]);
// 创建虚拟节点
const myVnode = h('a', {
props: {
href: 'http://www.xxx/com'
}
}, 'Hello');
// h函数可以嵌套使用
const myVnodeUl = h('ul', {}, [
h('li', {}, 'xxx'),
h('li', {}, [
h('p', {}, 'yyy')
]),
]);
// 虚拟节点上树
const container = document.getElemntById('container');
patch(container, myVnode);
节点的比较过程
Vue比较节点,会从父节点是相同节点的情况下再对他的子节点进行比较,也就是从父节点一层一层往下深度比较,同时会进行从左向右的遍历顺序比较节点。(从根节点开始)
从根节点开始,一层一层将新节点和旧节点开始比较,如果该节点新旧一致,则会继续向下面的子节点进行逐个比较。当遇到不同的子节点之后,不会再向下级比较。
处理新旧节点
- 不需要移动的相同节点则不移动
- 相同的需要移动的节点,移动
- 没有的节点进行新建和删除
Diff中的key
如果标签没有key值作区分,Diff的比较只会比较两个相同标签中的内容,直接覆盖内容。
如果标签中的内容在更改过程中加了过渡过程,在使用Key的情况下,才能触发过渡效果,不然则是Diff的简单内容覆盖。
Diff比较节点时不使用key的情况
在Diff比较节点时,遇到相同的节点会继续往后一个节点进行比较。如果此时遇到一个新旧节点不同的节点,则DOM中的该节点就会改变(无论新节点是刚刚插入或者是移动的位置)。
也就是说,当比较到节点不同时,新的节点会覆盖掉旧的节点,而不会管后面的节点是什么。
Diff比较节点时使用key的情况
节点使用key值之后,当Diff比较遇到不同的节点时,它不会立即改变,它会比较标签上的key值是否一致,不一致的话,暂时不会对当前标签进行改变。
如果之后遇到了同样标签同样key值的节点,则节点不会改变,只会改变新建或者移动了的带有key值的节点。在这种情况下,需要更新的节点,可能变得更少了。
Diff算法比较了新旧节点之后,将结果反馈给DOM树上,之后DOM树只会更改需要更改的部分。Diff算法也使新旧节点比较的时间复杂度缩短至O(n)。