简单实现diff算法-参考snabbdom库的实现

1. diff算法的作用:

  1. diff 即最小量更新,以最节省性能的方式更新dom;
  2. 会存在两个虚拟dom树,一个为旧的dom树,即更新前的dom树;一个为新的dom树,即将要更新为的dom树;
  3. 在dom更新时不会一次性删除所有旧的node节点来创建新的node节点,而是经过 四个命中规则来判断要更新的node节点,并实现不同的更新;
  4. 对已经存在的node节点如果顺序未发生变化则不做任何操作,如果只是顺序发生变化则使用insertBefore方法来移动node节点,如果只是内容文本发生了变化则只改变innerText,如果新增了节点则使用封装的createElement方法(这个createElement不是document的方法,而是自己封装的一个新的createElement方法)来创建真实dom节点并使用patchVnode方法将dom渲染到dom树中,如果删除了节点则使用removeChild方法对node节点进行删除

2. 四个命中规则:

大致的命中规则可以按照四个点和四个规则来实现

四个点:

  1. 旧首:即旧的虚拟dom的首部vnode节点
  2. 旧尾:即旧的虚拟dom的尾部vnode节点
  3. 新首:即新的虚拟dom的首部vnode节点
  4. 新尾:即新的虚拟dom的尾部vnode节点

新旧虚拟dom示例:

// 旧的虚拟dom
const oldVnode = h("ul", { class: { text: true } }, [
  h("li", { key: "A" }, "A"),// 旧首
  h("li", { key: "B" }, "B"),
  h("li", { key: "C" }, "C"),// 旧尾
]);
// 新的虚拟dom
const newVnode = h("ul", { class: { text: true } }, [
  h("li", { key: "G" }, "G"),// 新首
  h("li", { key: "C" }, "C"),
  h("li", { key: "E" }, "E"),
  h("li", { key: "D" }, "D"),
  h("li", { key: "F" }, "F"),// 新尾
]);

// h 函数解释:
/*
h 函数是 snabbdom 提供的一个创建 vnode 的函数(其内部还要通过 vnode 函数来生成 vnode对象 )
用法:
h('div', { class: { text: true }, key: 'A' }, 'hello world') 
生成的vnode对象为: { key: 'A', sel: 'div', data: { class: { text: true } }, elm: undefined, children: undefined, text: 'hello world'}
渲染的dom形式为: 
hello world
*/

四个规则:

  1. 新首 比较 旧首
    若相等则调用patchVnode函数更新dom,并且下移下标指针
  2. 新尾 比较 旧尾
    若相等则调用patchVnode函数更新dom,并且上移下标指针
  3. 新尾 比较 旧首
    若相等则调用patchVnode函数更新dom,移动未处理的vnode,插入旧尾的后边
  4. 新首 比较 旧尾
    若相等则调用patchVnode函数更新dom,移动未处理的vnode,插入旧首的前边
  5. 未匹配上的key不为undefined的vnode会存入一个keyMap集合, 判断新的虚拟dom中是否包含在keyMap中
    包含则调用patchVnode方法并且移动当前节点到旧首的前边
    如果不包含则将当前虚拟节点创建为真实节点并插入到旧首的前边
  6. 四个规则渲染完毕后
  1. 若旧的虚拟dom比新的虚拟dom先循环完毕则说明还有未渲染的dom, 则遍历这部分虚拟dom并渲染到新首的后边
  2. 若新的虚拟dom比旧的虚拟dom先循环完毕则说明有需要删除的dom, 则遍历这部分dom进行删除

总结:

  1. 当调用patch方法时会判断新旧VNode是否为同一个节点(标签sel和key相等视为同一个节点), 相同则调用patchVnode方法
  2. patchVnode做了以下操作:
    1. 找到对应的真实dom
    2. 如果都有文本节点且不相等,将真实dom的文本节点设置为newVnode的文本节点
    3. 如果oldVnode有子节点而newVNode没有,则删除真实dom中的子节点
    4. 如果oldVnode没有子节点而newVNode有,则将newVNode创建为真实的dom并添加在oldVnode的真实节点中
    5. 如果两者都有子节点,则执行updateChildren函数比较子节点
  3. updateChildren主要做了以下操作:
    1. 设置新旧VNode的头尾指针
    2. 新旧头尾指针进行比较,从两端往中间循环,根据情况调用patchVnode进行递归,发现新节点时调用createElement创建一个新节点,旧节点则会移动改节点位置到指定位置,从keyMap寻找 key一致的newVNode 节点再分情况操作

** gitee 仓库地址: https://gitee.com/git_zs/diff-algorithm.git **

你可能感兴趣的:(javascript,vue,react,算法,javascript,前端)