Simonzhangs' blog Simonzhangs' blog
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Simonzhangs

前端学习探索者
首页
  • 前端文章

    • HTML
    • CSS
    • JavaScript
  • 学习笔记

    • 《JavaScript教程》
    • 《JavaScript高级程序设计》
    • 《ES6 教程》
    • JS设计模式总结
  • 《Vue》
  • 《React》
  • 《TypeScript 从零实现 axios》
  • TypeScript
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • apple music
  • extension
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • 组件

  • 过渡&动画

  • 可复用性&组合

  • 工具

  • 规模化

  • Vuex

  • Pinia

  • 其他

    • 虚拟DOM和diff算法
      • 生成 VNode
      • patch 函数
      • patchVnode 函数
      • updateChildren 函数
    • keep-alive
    • Vue中的防抖函数封装和使用
    • 操作本地缓存
  • 《Vue》笔记
  • 其他
simonzhangs
2022-04-16
目录

虚拟DOM和diff算法

在使用 Vue 框架之前,我们需要用 JS 或者 jQuery 直接操作 DOM 从而进行视图的更新,要知道如何有多个数据发生变化的话,要直接操作 DOM 进行视图的更新会占用很多开销。而在 Vue 框架中,我们知道通过 data()中数据的改变进行视图的更新。其中具体的过程其实是:数据发生改变 --> 虚拟 DOM 计算变更 --> 操作真实 DOM --> 视图更新。

# 虚拟 DOM

虚拟 DOM 指的是用 JS 对象来模拟 DOM 结构,这个对象由三个重要属性:tag、props 和 children,分别对应 DOM 的标签、属性、子元素。我们以下面的 DOM 结构来书写一个虚拟 DOM,也就是 JS 对象:

<div id="id" class="container">
  <h1>虚拟DOM</h1>
  <ul style="color:orange">
    <li>第一项</li>
    <li>第二项</li>
    <li>第三项</li>
  </ul>
</div>
1
2
3
4
5
6
7
8

对应的虚拟 DOM 对象为:

{
    tag: 'div',
    props: {
        id: 'app',
        className: 'container'
    },
    children: [
        {
            tag: 'h1',
            children: '虚拟DOM'
        },
        {
            tag: 'ul',
            props: { style: 'color:orange'},
            children: [
                {
                    tag: 'li',
                    children: '第一项'
                },
                {
                    tag: 'li',
                    children: '第二项'
                },
                {
                    tag: 'li',
                    children: '第三项'
                }
            ]
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 虚拟 DOM 的好处

那为什么要用虚拟 DOM 呢,显而易见虚拟 DOM 实际上是 JS 对象,相比于直接操作 DOM 来更新视图,直接使用 JS 引擎操作 JS 对象会更快。此外,操作虚拟 DOM 更新视图,它不会将整个 DOM 节点重新渲染,只会渲染改变的 DOM 节点。

我们通过一个例子就可以得出很直观地看出虚拟 DOM 的优点:

# diff 算法

diff 算法是虚拟 DOM 的核心。 我们用虚拟 DOM 来表示真实 DOM,当数据发生改变时,会产生一个新的虚拟 DOM,通过对比新旧虚拟 DOM 的最小变化,从而更新视图。diff 算法则是用来查找新旧虚拟 DOM 的差异,它的步骤是:

  1. 遍历老虚拟 DOM
  2. 遍历新虚拟 DOM
  3. 重新排序

如果直接这样遍历比较的话,复杂度是 O(n^3),根据 DOM 树结构特点,对 diff 算法改进:

  1. 只比较同一层级,不跨级比较,复杂度则变为 O(n);
  2. 标签名不同,直接删除,不继续深度比较;
  3. 标签名相同,key 相同,就认为是相同节点,不继续深度比较;(v-for 绑定的:key)

# 虚拟 DOM 和 diff 流程

# 生成 VNode

vnode函数传入sel,data,children,text,undefined, 返回节点对象{sel, data, children, text, elm, key }

注意: children 和 text 只能存在一个;

elm 是虚拟节点sel 对应的真实的DOM节点, 在使用patch函数时使用,代表要渲染到哪一个真实DOM元素上;

key 是v-for 绑定的key, 其实在Vue每个组件都是可以接收key的,只不过v-for是必须提供key值。(为什么需要提供呢,在后面的函数中有提到)

# patch 函数

在首次页面渲染的时候执行一次patch函数,将VNode渲染到空的容器中:patch(container, vnode);这里container是真实DOM的节点。

之后页面更新时将新的VNode替换旧的VNode:patch(vnode, newVnode)

patch函数解析:

首先判断第一个参数是否为Vnode类型,如果不是则创建一个空的vnode,并关联到对应的DOM元素;

再通过sameVnode判断新旧vnode是否相同,比较的两个vnode的key和sel是否相等,如果相同的话就直接使用patchVnode更新;

如果不相同的话,说明两个vnode的key或tag不相同,是不同的Vnode,则使用createElm来创建一个真实的DOM元素,并插入新的DOM删除旧的DOM。

# patchVnode 函数

在新旧Vnode相同时候,就用patchNode进行更新。

首先将旧的vnode关联的DOM元素传递给新的vnode;

再判断新旧vnode的children是否相等,如果相等就直接返回;如果不相等的话,分为以下几种情况:

  1. 新的vnode 有text属性,无children属性,且新旧vnode的text属性不相同时

如果旧的vnode 有children属性,则移除旧的vnode的children属性移除;并且设置新的vnode的text

  1. 新的vnode 无text

①新旧vnode的children都有定义,则利用updateChildren对children进行更新;

②新的vnode有children,而旧的没有children,则清空旧节点的text,并新的children添加;

③新的vnode 无children,旧的有children,则移除旧的vnode;

④旧的vnode有text,则清空text。

# updateChildren 函数

这个是diff最小量更新的核心,通过四个指针来比较,分别是老的children的oldStartIdx、oldEndIdx,新children的newStartIdx、newEndIdx。

一共有四个对比情况,看是否vnode相同:

  1. 老的开始和新的开始对比

如果两者vnode相同,则进行patchVnode操作,又回去了进行节点下面的比较,相当于递归;然后对于指针进行移动。

  1. 老的结束和新的结束对比

  2. 老的开始和新的结束对比

  3. 老的结束和新的结束对比

如果上面四种情况都没有命中,则拿新的开始的key,在老的children里面去找有没有某个节点有对应的key:

  1. 如果在老的children中找到了:再判断sel是否相等,如果不相等,则创建新的元素,否则进行patchVnode对节点进行更新;

  2. 如果没有找到,则创建新的元素。

参考链接:15张图,20分钟吃透Diff算法核心原理,我说的!!!

编辑 (opens new window)
上次更新: 2022/04/17, 17:30:36
Vue3中状态管理pinia学习
keep-alive

← Vue3中状态管理pinia学习 keep-alive→

最近更新
01
一些有意思的类比
06-16
02
the-super-tiny-compiler解析
06-06
03
计算机编译原理总概
06-06
更多文章>
Theme by Vdoing | Copyright © 2021-2022
蜀ICP备2021023197号-2
Simonzhans | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
  • 飙升榜
  • 新歌榜
  • 云音乐民谣榜
  • 美国Billboard榜
  • UK排行榜周榜