前端八股整理|Vue|虚拟 DOM、Diff 与 Patch 流程

文章目录

  • [前端八股整理|Vue|虚拟 DOM、Diff 与 Patch 流程](#前端八股整理|Vue|虚拟 DOM、Diff 与 Patch 流程)
  • [1.什么是虚拟dom?虚拟 dom 的好处?](#1.什么是虚拟dom?虚拟 dom 的好处?)
  • [2. diff算法](#2. diff算法)
    • [2.1 patch 函数](#2.1 patch 函数)
    • [2.2 patchVnode 函数](#2.2 patchVnode 函数)
    • [2.3 updateChildren函数](#2.3 updateChildren函数)

前端八股整理|Vue|虚拟 DOM、Diff 与 Patch 流程

1.什么是虚拟dom?虚拟 dom 的好处?

参考视频:Vue中的虚拟DOM 虚拟DOM如何使用?有什么好处?

jQuery时代是操作 Dom->视图更新

vue 时代是数据改变->操作 Dom->视图更新,但是从实际上执行来说 运行 js要比操作 dom 快的多得多,所以引入了虚拟 Dom

数据改变->虚拟 DOM(计算变更)->操作真实的 DOM->视图更新

什么是虚拟Dom?

就是用 JS 模拟 DOM 结构.虚拟 DOM 的三个关键组成是 tag,props,children.其中 tag 就是代表这个 html 的标签是什么,props 里面放一些 id,className 就看标签上有哪些属性,children 就是这个标签里面嵌套了哪些内容.

html 对应的虚拟 Dom

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app" class="container">
        <h1>虚拟DOM</h1>
        <ul style="color: red;">
            <li>第一项</li>
            <li>第二项</li>
        </ul>
    </div>
</body>
</html>
javascript 复制代码
{
  tag:'div',
  props:{
    id:'app',
    className:'container'
  },
  children:[
    {
      tag:'h1',
   		children:'虚拟 DOM'
    },
    {
      tag:'ul',
      props:{style="color:red"},
      children:[
        {
          tag:'li',
          children:'第一项'
				},
        {
          tag:'li',
          children:'第二项'
        }
      ]
    }
  ]
}

虚拟 dom 的好处:

比如更新 dom,虚拟dom 会计算一个最小的更新视图,再去操作 dom,比如有两个 dom 节点,现在修改 dom,第一项不变,第二项修改内容,第三项新增,使用虚拟 dom,最后只会更新第二项和第三项

2. diff算法

理论上,比较两棵树的最小差异是一个 O(n³) 的复杂问题(经典"树编辑距离"问题)。

对前端 DOM 来说,O(n³) 根本不可接受(n=1000 时已经算不动)。

Vue 采用了 启发式策略:只比较同层级节点,不做跨层比较 → 复杂度降低为 O(n)。

只比较同一层级

同层比较时

  • 标签名(tag)不同,直接删除,不继续深度比较
  • tag 相同但 key 不同:通常也认为不是同一个节点
  • 标签名(tag)相同,key相同,可以认为是相同节点,继续比较 props 和 children

虚拟 dom 节点 vnode 是怎么创建出来的?

typescript 复制代码
return {sel,data,children,text,elm,key}
  • sel:选择器,例如 div
  • data:比如样式
  • children:一个数组,包含了他的子节点
  • text:和 children 是对立的,两者只存在一个,text 就是纯文本
  • elm:就是要把这个节点渲染到某个真实的 dom 节点上的 dom 节点
  • key:v-for 提供的那个 key

2.1 patch 函数

patch 函数

首次页面渲染的时候去执行一次,目的是给他渲染到一个空的容器里来

javascript 复制代码
patch(container,vnode)

后面页面更新的时候,会把新的 vnode 去替换掉老的 vnode

javascript 复制代码
patch(vnode,newVnode)

patch 函数的过程

patch(oldVnode,vnode)

  • 首先就是如果传入的第一个参数不是 vnode,对应的就是首次渲染传入 container 的情况,此时就会创建空的 vnode,并关联 DOM元素,这样后续更新的时候就知道我们要更新在哪个 dom元素上
  • 接下来,判断传入的 oldvnode 和 vnode 是不是同一个节点,比较的就是 key 和sel,如果此时相同,就做更新 patchVnode(oldVnode,vnode)如果不相同,就会创建一个新的 DOM 元素,再插入这个新的 DOM 元素,最后移除老的 DOM 元素

根据新的虚拟 DOM,重新创建一个新的真实 DOM 节点,然后把旧的真实 DOM 替换掉。

2.2 patchVnode 函数

patchVnode 函数的过程

  • 首先就是给新的 vnode 挂上老的 vnode 对应的真实 dom 元素,也就是那个 elm,因为新的 vnode 很可能不知道之前要挂在哪个真实的 dom 节点上

  • 然后就是比较老的 vnode 和新的 vnode 的 children,如果相同,就直接返回

  • 接下来就是很重要的逻辑,children 不同的那些情况都怎么去判断实现

    • 新 vnode 无 text

      • 新 vnode有 children
        • 老的 vnode也有children,此时就要用 updateChildren 做更新
        • 老的 vnode 没有children,但是有 text,那么就将真实 dom 元素中的 text清空,将新的 vnode 的 children 添加到真实的 dom 上
      • 新 vnode没有 children
        • 这种情况应该是一个空节点,如果老的存在就对应的在真实 dom 上删掉即可
    • 新的 vnode 有 text,没有 children

      • 老的 vnode 存在 text,首先做比较,老的 vnode 的 text不等于新的vnode 的 text,此时如果老的 vnode 里面有 children,则删除真实 dom 元素的 children,设置真实的 dom 元素的 text 为新 vnode 的 text
      • 老的 vnode 不存在 text,那真实 dom 就更新成新 vnode,老的 vnode 有 children ,真实 DOM 里的 children 清掉,再设置 text

      注意:

      text 和children 根据 vnode 的定义,两者只会出现一个,是对立的,切记!

2.3 updateChildren函数

updateChildren函数的过程:

javascript 复制代码
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  // 依次尝试 4 种命中
}

循环的主控条件是两边都没处理完

4 种命中分别是

  • 新头比旧头
  • 新头比旧尾
  • 新尾比旧头
  • 新尾比旧尾

如果都没命中,走兜底操作,比较 key 值

为什么要一轮一轮的进行四种命中呢?

因为这是一种很省成本的启发式策略,本质在赌一个事实,前端列表更新里,很多变动其实都发生在头尾附近。比如头部插入,尾部追加等等.优先处理最常见的场景

如果这 4 种方式命中了,命中后先 patchVnode,必要时做 DOM 移动,然后再移动对应指针。那么就是对应的指针++或者--

比如新头和旧尾命中了,那就是 newStartIdx++,oldEndIdx--

如果没命中,那就以新头指针为一个标志,拿着他的 key,去旧的列表中去找可复用的节点,如果没找到就创建新的,如果找到了但是tag 不同,那也创建新的,如果相同,也是继续去调用patchVnode 函数进行比较,

对应的指针 newStartIdx肯定是++,旧列表的对应元素,设置一个记号,被复用的位置标记为空位 / undefined,表示这个位置已经处理过了,后续跳过(在匹配上的情况)

相关推荐
卸任2 小时前
Vibe coding工具使用心得
前端·ai编程
brien93 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance3 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M3 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写3 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李3 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易3 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding3 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马4 小时前
前沿视界
前端
kyriewen4 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员