关于Diff算法

155 阅读4分钟

什么是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比较节点,会从父节点是相同节点的情况下再对他的子节点进行比较,也就是从父节点一层一层往下深度比较,同时会进行从左向右的遍历顺序比较节点。(从根节点开始)

从根节点开始,一层一层将新节点和旧节点开始比较,如果该节点新旧一致,则会继续向下面的子节点进行逐个比较。当遇到不同的子节点之后,不会再向下级比较。

处理新旧节点

  1. 不需要移动的相同节点则不移动
  2. 相同的需要移动的节点,移动
  3. 没有的节点进行新建和删除

Diff中的key

如果标签没有key值作区分,Diff的比较只会比较两个相同标签中的内容,直接覆盖内容。

如果标签中的内容在更改过程中加了过渡过程,在使用Key的情况下,才能触发过渡效果,不然则是Diff的简单内容覆盖。

Diff比较节点时不使用key的情况

在Diff比较节点时,遇到相同的节点会继续往后一个节点进行比较。如果此时遇到一个新旧节点不同的节点,则DOM中的该节点就会改变(无论新节点是刚刚插入或者是移动的位置)。

也就是说,当比较到节点不同时,新的节点会覆盖掉旧的节点,而不会管后面的节点是什么。

Diff比较节点时使用key的情况

节点使用key值之后,当Diff比较遇到不同的节点时,它不会立即改变,它会比较标签上的key值是否一致,不一致的话,暂时不会对当前标签进行改变。

如果之后遇到了同样标签同样key值的节点,则节点不会改变,只会改变新建或者移动了的带有key值的节点。在这种情况下,需要更新的节点,可能变得更少了。

Diff算法比较了新旧节点之后,将结果反馈给DOM树上,之后DOM树只会更改需要更改的部分。Diff算法也使新旧节点比较的时间复杂度缩短至O(n)。