说一下Diff算法?| 面试题

233 阅读4分钟

你了解vue中的diff算法吗?

题目分析:vue基于虚拟DOM做更新,diff又是其核心部分,因此常被问道,此题考查面试者深度。

分析

必问题目,涉及vue更新原理,比较考查理解深度。

思路

  1. diff算法是干什么的
  2. 它的必要性
  3. 它何时执行
  4. 具体执行方式
  5. 拔高:说一下vue3中的优化

回答范例

1.Vue中的diff算法称为patching算法,它由Snabbdom修改而来,虚拟DOM要想转化为真实DOM就需要通过patch方法转换。

2.最初Vue1.x视图中每个依赖均有更新函数对应,可以做到精准更新,因此并不需要虚拟DOM和patching算法支持,但是这样粒度过细导致Vue1.x无法承载较大应用;Vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,此时就需要引入patching算法才能精确找到发生变化的地方并高效更新。

3.vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟DOM,然后执行patch函数,并传入新旧两次虚拟DOM,通过比对两者找到变化的地方,最后将其转化为对应的DOM操作。

4.patch过程是一个递归过程,遵循深度优先、同层比较的策略;以vue3的patch为例:

  • 首先判断两个节点是否为相同同类节点,不同则删除重新创建

  • 如果双方都是文本则更新文本内容

  • 如果双方都是元素节点则递归更新子元素,同时更新元素属性

  • 更新子节点时又分了几种情况:

    • 新的子节点是文本,老的子节点是数组则清空,并设置文本;
    • 新的子节点是文本,老的子节点是文本则直接更新文本;
    • 新的子节点是数组,老的子节点是文本则清空文本,并创建新子节点数组中的子元素;
    • 新的子节点是数组,老的子节点也是数组,那么比较两组子节点,更新细节blabla
  1. vue3中引入的更新策略:编译期优化patchFlags、block等

知其所以然

patch关键代码

github1s.com/vuejs/core/…


说一下Diff算法?

要点: patch、patchVnode、updateChildren、vue优化时间复杂度为O(n)

答:

Diff算法比较过程

第一步: patch函数中对新老节点进行比较。如果新节点不存在就销毁老节点;如果老节点不存在,直接创建新的节点;当两个节点是相同节点的时候,进入 patctVnode 的过程,比较两个节点的内部。

第二步: patchVnode函数比较两个虚拟节点内部。如果两个虚拟节点完全相同,返回;当前vnode 的children 不是textNode,再分成三种情况:

  • 有新children,没有旧children,创建新的;
  • 没有新children,有旧children,删除旧的 ;
  • 新children、旧children都有,执行updateChildren比较children的差异,这里就是diff算法的核心。当前vnode 的children 是textNode,直接更新text。

第三步: updateChildren函数子节点进行比较

  • 第一步 头头比较。若相似,旧头新头指针后移(即 oldStartIdx++ && newStartIdx++),真实dom不变,进入下一次循环;不相似,进入第二步。
  • 第二步 尾尾比较。若相似,旧尾新尾指针前移(即 oldEndIdx-- && newEndIdx--),真实dom不变,进入下一次循环;不相似,进入第三步。
  • 第三步 头尾比较。若相似,旧头指针后移,新尾指针前移(即 oldStartIdx++ && newEndIdx--),未确认dom序列中的头移到尾,进入下一次循环;不相似,进入第四步。
  • 第四步 尾头比较。若相似,旧尾指针前移,新头指针后移(即 oldEndIdx-- && newStartIdx++),未确认dom序列中的尾移到头,进入下一次循环;不相似,进入第五步。
  • 第五步 若节点有key且在旧子节点数组中找到sameVnode(tag和key都一致),则将其dom移动到当前真实dom序列的头部,新头指针后移(即 newStartIdx++);否则,vnode对应的dom(vnode[newStartIdx].elm)插入当前真实dom序列的头部,新头指针后移(即 newStartIdx++)。
  • 但结束循环后,有两种情况需要考虑:
    • 新的字节点数组(newCh)被遍历完(newStartIdx > newEndIdx)。那就需要把多余的旧dom(oldStartIdx -> oldEndIdx)都删除,上述例子中就是c,d
    • 新的字节点数组(oldCh)被遍历完(oldStartIdx > oldEndIdx)。那就需要把多余的新dom(newStartIdx -> newEndIdx)都添加。

1.出现:主流框架中多采用VNode更新结点,更新规则为diff算法。
2.原理:框架会将所有的结点先转化为虚拟节点Vnode,在发生更改后将VNode和原本页面的OldNode进行对比,然后以VNode为基准,在oldNode上进行准确的修改。(修改准则:原本没有新版有,则增加;原本有新版没有,则删除;都有则进行比较,都为文本结点则替换值;都为静态资源不处理;都为正常结点则替换)