Vue中的虚拟Dom,diff算法,以及diff的优化

news2024/10/7 4:31:22

virtual dom:

关键词:
1、 template
2、渲染函数
3、 vnode(virtual dom)
4、patch(diff算法)
5、view
在这里插入图片描述

Vue.js通过编译将template 模板转换成渲染函数(render ) ,执行渲染函数就可以得到一个虚拟节点树
VNode 虚拟节点:它可以代表一个真实的 dom 节点。通过 createElement 方法能将 VNode 渲染成 dom 节点。简单地说,vnode可以理解成节点描述对象,它描述了应该怎样去创建真实的DOM节点。
patch(也叫做patching算法):虚拟DOM最核心的部分,它可以将vnode渲染成真实的DOM,这个过程是对比新旧虚拟节点之间有哪些不同,然后根据对比结果找出需要更新的的节点进行更新。这点我们从单词含义就可以看出, patch本身就有补丁、修补的意思,其实际作用是在现有DOM上进行修改来实现更新视图的目的。Vue的Virtual DOM Patching算法是基于Snabbdom的实现,并在些基础上作了很多的调整和改进
在这里插入图片描述

虚拟DOM在Vue.js主要做了两件事:

1、提供与真实DOM节点所对应的虚拟节点vnode

2、将虚拟节点vnode和旧虚拟节点oldVnode进行对比(diff算法),然后更新视图

总结:「vdom是为了减轻性能压力。dom是昂贵的,昂贵的一方面在dom本身的重量,dom节点在js里的描述是个非常复杂属性很多原型很长的超级对象,另一方面是浏览器渲染进程和js进程是独立分离的,操作dom时的通信和浏览器本身需要重绘的时耗都是很高的。所以用vdom去模拟dom,vdom每个节点都只挂载js操作的必要属性,每次组件update时都先操作vdom,通过vdom的比对,得到一个真实dom的需要操作的集合。整个机制是在JavaScript层面计算,在完成之前并不会操作DOM,等数据稳定之后再实现精准的修改。」

什么是虚拟DOM:

虚拟DOM就是普通的js对象。是一个用来描述真实dom结构的js对象,它不是真实的dom
1、传统的dom数据发生变化的时,我们需要不断的去操作dom,才能更新数据
2、 虽然出现了模板引擎,可以一次性更新多个dom。但它没有可以追踪状态的机制,当引擎内某个数据发生变化时,它依然操作dom去重新渲染整个引擎。
3、而vue借助和改造Snabbdom便是虚拟dom,它可以跟踪当前dom状态,因为它会根据当前数据生成一个描述当前dom结构的虚拟dom,当数据发生变化时,又生成一个新的虚拟dom,而两个虚拟dom恰好保存了变化前后的状态。
4、然后通过diff算法,计算出当前两个虚拟dom之间的差异,得出一个更好的替换方案,解决更新页面时的性能问题


# Snabbdom的使用: 流程
// 引入对应功能(核心功能Init,h)
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

// 1.通过init  获取到patch函数
const patch = init([])

// 2.通过h创建vNode
let vNode = h('div.app', '新内容')
  
// 3.获取挂载元素
const dom = document.querySelector('#app')

// 4.通过patch,将vNode渲染到dom上
const oldValue = patch(dom, vNode)

// 5.创建新的vnode, 更新到oldValue
vNode = h('p#text.abc', 'p标签内容')
patch(oldValue, vNode)

官方提供:

6个模块如下:
1、 attributes 设置DOM元素的属性,使用setAttribute(); 处理布尔类型的属性
2、props 和attributes模块相似,设置DOM元素的属性element[attr] = value; 不处理布尔类型的属性
3、 class 切换类样式 注意:给元素设置类样式是通过sel选择器
4、设置 data-* 的自定义属性
5、eventlisteners 注册和移除事件
6、style 设置行内样式, 支持动画delayed/remove/destroy

Snabbdom的核心:

介绍如下:
1、init函数 初始化一个patch函数
2、h函数 创建vNode虚拟Dom节点
3、patch() 比较新旧两个 Vnode
4、把变化的内容更新到真实 DOM 树,并渲染到视图上


# patch 整体过程:

分析:
• patch(oldVnode, newVnode)
• 把新节点中变化的内容渲染到真实 DOM,最后返回新节点作为下一次 处理的旧节点
• 对比新旧 VNode 是否相同节点(节点的 key 和 sel 相同)
• 如果不是相同节点,删除之前的内容,重新渲染
• 如果是相同节点,再判断新的 VNode 是否有 text,如果有并且和 oldVnode 的 text 不同,直接更新文本内容
• 如果新的 VNode 有 children,判断子节点是否有变化


diff算法:比较两棵虚拟DOM树的差异

我们知道了,新的虚拟DOM和旧的虚拟DOM是通过diff算法进行比较之后更新的。
这也是 Virtual DOM 算法最核心的部分。两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比:

在这里插入图片描述
上面的div只会和同一层级的div对比,第二层级的只会跟第二层级对比。这样算法复杂度就可以达到 O(n)。
2.1 深度优先遍历,记录差异

在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记:
在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比。如果有差异的话就记录到一个对象里面。

// diff 函数,对比两棵树
function diff (oldTree, newTree) {
  var index = 0 // 当前节点的标志
  var patches = {} // 用来记录每个节点差异的对象
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}

// 对两棵树进行深度优先遍历
function dfsWalk (oldNode, newNode, index, patches) {
  // 对比oldNode和newNode的不同,记录下来
  patches[index] = [...]

  diffChildren(oldNode.children, newNode.children, index, patches)
}

// 遍历子节点
function diffChildren (oldChildren, newChildren, index, patches) {
  var leftNode = null
  var currentNodeIndex = index
  oldChildren.forEach(function (child, i) {
    var newChild = newChildren[i]
    currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标识
      ? currentNodeIndex + leftNode.count + 1
      : currentNodeIndex + 1
    dfsWalk(child, newChild, currentNodeIndex, patches) // 深度遍历子节点
    leftNode = child
  })
}

例如,上面的div和新的div有差异,当前的标记是0,那么:

patches[0] = [{difference}, {difference}, ...] // 用数组存储新旧节点的不同

同理p是patches[1],ul是patches[3],类推。

Vue3.0 diff:

diff痛点:
• vue2.x中的虚拟dom是进行「全量的对比」,在运行时会对所有节点生成一个虚拟节点树,当页面数据发生变更好,会遍历判断virtual dom所有节点(包括一些不会变化的节点)有没有发生变化;
• Vue2.x的diff算法,会不断地递归调用 patchVNode,不断堆叠而成的几毫秒,最终就会造成 VNode 更新缓慢
• Vue3.0 动静结合 PatchFlag;这个模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。「也就是在生成VNode的时候,同时打上标记,在这个基础上再进行核心的diff算法」并且 PatchFlag 会标识动态的属性类型有哪些


export const enum PatchFlags {
  TEXT = 1,
  CLASS = 1 << 1,
  STYLE = 1 << 2,
  PROPS = 1 << 3,
  FULL_PROPS = 1 << 4,
  HYDRATE_EVENTS = 1 << 5,
  STABLE_FRAGMENT = 1 << 6,
  KEYED_FRAGMENT = 1 << 7,
  UNKEYED_FRAGMENT = 1 << 8,
  NEED_PATCH = 1 << 9,
  DYNAMIC_SLOTS = 1 << 10,
  DEV_ROOT_FRAGMENT = 1 << 11,
  HOISTED = -1,
  BAIL = -2
}

在这里插入图片描述
当 patchFlag 的值「大于」 0 时,代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。

当 patchFlag 的值「小于」 0 时,代表所对应的元素在 patchVNode 时,是需要被 full diff,即进行递归遍历 VNode tree 的比较更新过程。

看源码:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
 return (_openBlock(), _createBlock("div", null, [
  _createVNode("p", null, "'HelloWorld'"),
  _createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
 
 ]))
}

这里的_createVNode(“p”, null, _toDisplayString(_ctx.msg), 1 /* TEXT */)就是对变量节点进行标记。

总结:「Vue3.0对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用。」

其实diff算法对比的是虚拟Dom,而虚拟Dom是呈树状的,所以我们可以发现,diff算法中充满了递归。总结起来,其实diff算法就是一个 patch —> patchVnode —> updateChildren —> patchVnode —> updateChildren —> patchVnode这样的一个循环递归的过程。

循环中 我们经常用到key,key的主要作用其实就是对比两个虚拟节点时,判断其是否为相同节点。加了key以后,我们可以更为明确的判断两个节点是否为同一个虚拟节点,是的话判断子节点是否有变更(有变更更新真实Dom),不是的话继续比。如果不加key的话,如果两个不同节点的标签名恰好相同,那么就会被判定为同一个节点(key都为undefined),结果一对比这两个节点的子节点发现不一样,这样会凭空增加很多对真实Dom的操作,从而导致页面更频繁得进行重绘和回流。

所以合理利用key可以有效减少真实Dom的变动,从而减少页面重绘和回流的频率,进而提高页面更新的效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/650394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 中的新成员

C 中的动态内存分配 C 中通过 new 关键字进行动态内存申请 C 中的动态内存申请是基于类型进行的 delete 关键字用于内存释放 new 关键字和 malloc 函数的区别 new 关键字是 C 的一部分 malloc 是由 C 库提供的函数 new 以具体类型为单位进行内存分配 malloc 以字节为单位…

Jenkins ——pipeline入门教程

一、什么是pipeline 什么是Pipeline&#xff1f;简单来说&#xff0c;就是一套运行于Jenkins上的工作流框架&#xff0c;将原本独立运行于单个或者多个节点的任务连接起来&#xff0c;实现单个任务难以完成的复杂发布流程&#xff08;实用场景&#xff1a;将多个Jenkins构建任…

最优化方法Python计算:一元函数搜索算法——二次插值法

已知连续函数 f ( x ) f(x) f(x)在 x ∗ x^* x∗近旁存在最优解 x 0 x_0 x0​。对博文《最优化方法Python计算&#xff1a;连续函数的单峰区间计算》讨论的 f ( x ) f(x) f(x)单峰区间的包围算法稍加修改&#xff0c;可算得 f ( x ) f(x) f(x)包含 x 0 x_0 x0​的单峰区间 [ a …

pandas---删除重复行、映射、异常值检测与过滤、抽样

1. 删除重复行 使用duplicated()函数检测重复的行。 返回布尔类型的Series对象&#xff0c;每个元素对应一行&#xff0c;如果该行不是第一次出现&#xff0c;则元素为True。 def make_df(indexs, columns): data [[str(j)str(i) for j in columns] for i in indexs]df …

中国人民大学与加拿大女王大学金融硕士——用更长远的眼光,展望未来

职场中遇到瓶颈&#xff0c;大家都迫切希望改变自己所处的环境&#xff0c;但却不愿意改变自己&#xff0c;所以他们自己仍然是被束缚的。如果一个人不能够从自我拷问的状态中解脱出来&#xff0c;他就永远也不可能实现自己心中的目标。我们要用更长远的眼光去展望未来&#xf…

NAVIGATE 领航者峰会:记忆科技携手新华三,以存储创新释放数据价值

近日&#xff0c;由紫光集团和新华三集团主办的2023 NAVIGATE 领航者峰会在杭州举行。本届峰会的主题为“精耕务实&#xff0c;为时代赋智慧”&#xff0c;围绕该主题&#xff0c;国内外数千名技术领导者汇聚一堂&#xff0c;探讨数字经济的创新未来。作为IT硬件领域的重要厂商…

vue + g6 实现树级结构(compactBox 紧凑树)

G6文档 自定义节点 G6.registerNode("dom-node",{draw: (cfg, group) > {let str <div classitem-box catalog-node ${cfg.isSelected ? "is-selected" : ""} ${cfg.status}-box οnclickhandleDetail("${cfg.id}") id&quo…

JMeter压测如何分配业务比例?

在进行综合场景压测时&#xff0c;由于不同的请求&#xff0c;要求所占比例不同&#xff0c;那如何实现呢&#xff1f; 有人说将这些请求分别放到单独的线程组下&#xff0c;然后将线程组的线程数按照比例进行配置&#xff0c;这种方法不是很好&#xff0c;想想&#xff0c;不…

5G是如何提升通行能力的?5G毫米波到底有多快?

高速公路&#xff0c;可以通过多层交通、多条车道、车道方向、车辆容量、货物包装、驾驶司机等多个因素&#xff0c;提升通行能力。 我们把5G比作高速公路&#xff0c;那么&#xff0c;5G是如何提升自身通行能力的呢&#xff1f;5G毫米波&#xff0c;到底能有多快呢&#xff1f…

跨越时空的教育:在线培训系统的全球化

随着全球化的发展&#xff0c;跨越时空的教育已经成为现实。在线培训系统可以打破地域限制&#xff0c;让学生能够接受来自世界各地的教育资源。这种新型教育模式具有巨大的潜力和优势。 在线培训系统是指通过互联网提供的远程教育服务。它可以通过网络平台、视频教育、虚拟课…

如何优雅地使用Low Code提高开发效率

2023年&#xff0c;低代码热度有&#xff0c;但是在企业内部核心场景的落地比例不高&#xff0c;推进进展也没有想象中快。就算是这样&#xff0c;低代码赛道也在“暗流涌动”。 数字化趋势下&#xff0c;很多企业想要以数字化的手段进行降本增效。很多企业希望以低代码的模式…

【Turfjs的java版本JTS】前面讲了Turfjs可以实现几何计算,空间计算的功能,如果后端要做这项功能也有类似的类库,JTS

JTS Java Topology Suite 几何计算&#xff1a; 1. 前端js就用这个 Turfjs的类库。参考网站&#xff1a; 计算两线段相交点 | Turf.js中文网 2. 后端java语言就可以用 JTS这个类库&#xff0c;参考网站&#xff1a; JTS参考网站&#xff1a; 1. https://github.com/locatio…

【机器学习】神经网络代价函数和反向传播算法

神经网络的代价函数 接下来我会再规定若干符号代表的含义&#xff1a; L L L表示神经网络的总层数 s i s_i si​表示的是第i层的神经元数量 如果神经网络处理的是一个二元分类问题&#xff0c;那么他的第L层就只会有一个节点&#xff1b;如果处理的是一个多元分类问题&…

不知不觉创作一年了,谈谈我的创作经历

前言 大家好&#xff0c;我是小刘在C站&#xff0c;不知不觉创作1年啦&#xff0c;本次文章呢分享一下我这一路走来的经历吧 目录 前言 1.为什么写博客 2.第一篇文章 3.怎么坚持创作的&#xff1f; 4.自我介绍 5.收获 6.认识了哪些大佬呢&#xff1f; 7.未来规划 8.分…

【C++】STL的string容器介绍

目录 1、string容器 1.1声明一个c字符串 1.2string和c字符数组的比较 1.3string类操作函数介绍 1.3.1赋值操作 1.3.2字符串拼接 1.3.3字符串查找 1.3.4字符串替换 1.3.5字符串比较 1.3.6字符存取 1.3.7字符串插入 1.3.8字符串删除 1.3.9子串获取 1、string容器 在…

测试4年外包已上岸 , 我只能说这类公司能不去尽量别去···

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是4年。现在终于跳槽到了互联网公司了&#xff0c;我想说的是&#xff0c;但凡有点机会&#xff0c;千万…

从零开始Vue项目中使用MapboxGL开发三维地图教程(五)实现框选要素功能、可拖动点展示坐标以及地图上实时更新要素

文章目录 1、实现框选要素功能1.1、添加点数据的图层&#xff1a;1.2、增加绘图插件&#xff08;mapbox-draw&#xff09;1.3、实现框选并让选择的目标数据高亮 2、实现地图上可拖动点2.1、实现功能&#xff1a;2.2、实现思路&#xff1a;2.3、代码示例&#xff1a; 3、实时更新…

已安装过PageOfiice,谷歌浏览器反复提示PageOffice安装

原因&#xff1a;Chrome开发团队以网络安全为由&#xff0c;强推ssl证书&#xff0c;希望所有部署在公网的网站&#xff0c;全部改用https访问&#xff0c;所以最新的谷歌和edge升级到94版本后对公网上的http请求下的非同域的http请求进行了拦截&#xff0c;于是就出现了目前遇…

火灾发生时如何实时地选择逃生路线

安科瑞虞佳豪 南京大学无菌动物房改造项目&#xff0c;位于位于南京江北新区学府路 12 号。改造面积约为 1100m2&#xff0c;均在原有建筑底层。其中&#xff0c;动物房区域含饲养室 6 间&#xff0c;层高 4.9m。功能实验区域含实验室 4间、手术室 1 间、暂养室 2 间、内外准备…

Linux进程信号 | 信号产生

前面的文章中我们讲述了进程间通信的部分内容&#xff0c;在本文中我们继续来学习进程信号相关的知识点。 信号入门 生活角度的信号 在我们的日常生活中&#xff0c;就有着各种各样的信号&#xff0c;它会给我们传递各种各样的信息&#xff0c;可以知道的是一个信号例如&…