传统diff算法复杂度o(n^3)
对比两棵树所有的节点,传统的方式一次比较两棵树的每一个节点,时间复杂度为o(n^2)。比较完之后,再把找到的差异循环更新(移动,创建,删除时需要遍历一次)到都没上去,又有循环一次,所以总的时间复杂度为o(n^3)
三个层级
- Tree Diff(层级比较)
• 先进行树结构的层级比较,对同一个父节点下的所有子节点进行比较;
• 接着看节点是什么类型的,是组件就做 Component Diff;
• 如果节点是标签或者元素,就做 Element Diff; - Component Diff (组件比较)
• 若组件类型相同,则继续按照层级比较其虚拟 DOM的结构;
• 如果组件类型不同,则替换整个组件的所有内容 Element Diff (元素比较)
• 如果节点是原生标签,则看标签名做比较是否相同来决定替换还是更新属性
• 然后进入标签后代递归 Tree Diff步骤
patch(oldVnode,newVnode)
- 打补丁,把新节点变化的内容渲染到真实DOM
- 对比新旧节点是否是相同的节点
- 如果不是相同节点,删除之前的内容,重新渲染
- 如果是相同节点,再判断新的vnode是否有text,如果有并且和oldVnode的text不同,直接更新文本
- 如果新的Vnode有children,判断子节点是够有变化,递归调用patchVnode
-
patch
功能:
传入新的Vnode,对比差异,把差异渲染到DOM
- 返回新的Vnode,作为下一次patch的oldVnode
执行过程
- 首先执行模块中的钩子函数pre
- 如果oldVnode和vnode相同
- 调用patchVnode,找到节点并更新DOM
- 如果oldVnode和vnode相同
如果oldVnode是DOM元素
首先执行用户设置的prepatch钩子函数
- 执行create钩子函数
- 首先执行模块的create钩子函数
- 然后执行用户设置的create钩子函数
- 如果vnode.text未定义
- 如果oldVnode.children和vnode.children都有值
- 调用updateChildren()
- 使用diff算法对比子节点,更新子节点
- 如果vnode.children有值,oldVnode.children无值
- 清空DOM元素
- 调用addVnodes,批量添加子节点
- 如果oldVnode.children有值,vnode.children无值
- 调用removeVnodes,批量移除子节点
- 如果oldVnode.text有值
- 清空DOM元素的内用
- 如果oldVnode.children和vnode.children都有值
- 如果vnode.text有值,并且和oldVnode。text不等
- 如果老节点有子节点,全部移除
- 设置DOM元素的textContent为vnode.text
-
updateChildren
功能:
diff算法的核心,对比新旧节点的children,更新DOM
执行过程:
- 在进行同级比较的时候,首先会对新老节点数组的开始和结尾节点设置标记索引,遍历的过程中移动索引
- 在开始和结束节点对比的时候,总共有四种情况
- 旧开始节点和新开始节点
- 旧结束节点和新结束节点
- 旧开始节点和新结束节点
- 旧结束节点和新开始节点
- 如果oldVnode和newVnode是sameVnode
- 调用patchVnode进行和更新节点
- 同时把移动新旧节点的索引
- 如果不是以上四种情况
- 遍历新节点,使用newStartNode的key在老节点数组中找到相同节点
- 如果没有找到,说明newStartNode是新节点
- 创建新节点对应的DOM于是怒,插入到DOM树中
- 如果找到了
- 判断新节点和找到的老节点的sel选择器是否相同
- 如果不相同,说明改节点被修改了
- 重新创建对应的DOM元素,插入到DOM树中
- 如果相同,把eleToMove对应的元素,移动到左边
- 循环结束
当老节点的所有子节点先遍历完,循环结束,说明新节点有剩余,把剩余节点批量插入到右边
当新节点的所有子节点先遍历完,循环结束,说明老节点有剩余,直接把老节点的剩余节点删除。