Vue diff算法相关问题

Vue为什么需要虚拟DOM?优缺点有哪些

由于在浏览器中操作 DOM 是很昂贵的。频繁的操作 DOM,会产生一定的性能问题。这就是虚拟 Dom 的产生原因。Vue2 的 Virtual DOM 借鉴了开源库 snabbdom 的实现。Virtual DOM 本质就是用一个原生的 JS 对象去描述一个 DOM 节点,是对真实 DOM 的一层抽象

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

  • 无法进行极致优化:虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
  • 首次渲染大量DOM时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢。

虚拟 DOM 实现原理?

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

回答范例

  1. 虚拟dom顾名思义就是虚拟的dom对象,它本身就是一个 JavaScript 对象,只不过它是通过不同的属性去描述一个视图结构
  2. 通过引入vdom我们可以获得如下好处:
    • 将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能
      • 直接操作 dom 是有限制的,比如:diff、clone 等操作,一个真实元素上有许多的内容,如果直接对其进行 diff 操作,会去额外 diff 一些没有必要的内容;同样的,如果需要进行 clone 那么需要将其全部内容进行复制,这也是没必要的。但是,如果将这些操作转移到 JavaScript 对象上,那么就会变得简单了
      • 操作 dom 是比较昂贵的操作,频繁的dom操作容易引起页面的重绘和回流,但是通过抽象 VNode 进行中间处理,可以有效减少直接操作dom的次数,从而减少页面重绘和回流
    • 方便实现跨平台
      • 同一 VNode 节点可以渲染成不同平台上的对应的内容,比如:渲染在浏览器是 dom 元素节点,渲染在 Native( iOS、Android)变为对应的控件、可以实现 SSR 、渲染到 WebGL 中等等
      • Vue3 中允许开发者基于 VNode 实现自定义渲染器(renderer),以便于针对不同平台进行渲染
  3. vdom如何生成?在vue中我们常常会为组件编写模板 - template, 这个模板会被编译器 - compiler编译为渲染函数,在接下来的挂载(mount)过程中会调用render函数,返回的对象就是虚拟dom。但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。
  4. 挂载过程结束后,vue程序进入更新流程。如果某些响应式数据发生变化,将会引起组件重新render,此时就会生成新的vdom,和上一次的渲染结果diff就能得到变化的地方,从而转换为最小量的dom操作,高效更新视图

为什么要用vdom?案例解析

[    { name: "张三", age: "20", address: "北京"},    { name: "李四", age: "21", address: "武汉"},    { name: "王五", age: "22", address: "杭州"},
]

将该数据展示成一个表格,并且随便修改一个信息,表格也跟着修改。 用jQuery实现如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head><body><div id="container"></div><button id="btn-change">改变</button><script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script><script>const data = [{name: "张三",age: "20",address: "北京"},{name: "李四",age: "21",address: "武汉"},{name: "王五",age: "22",address: "杭州"},];//渲染函数function render(data) {const $container = $('#container');$container.html('');const $table = $('<table>');// 重绘一次$table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'));data.forEach(item => {//每次进入都重绘$table.append($(`<tr><td>${item.name}</td><td>${item.age}</td><td>${item.address}</td></tr>`))})$container.append($table);}$('#btn-change').click(function () {data[1].age = 30;data[2].address = '深圳';render(data);});</script>
</body>
</html>
  • 这样点击按钮,会有相应的视图变化,但是你审查以下元素,每次改动之后,table标签都得重新创建,也就是说table下面的每一个栏目,不管是数据是否和原来一样,都得重新渲染,这并不是理想中的情况,当其中的一栏数据和原来一样,我们希望这一栏不要重新渲染,因为DOM重绘相当消耗浏览器性能。
  • 因此我们采用JS对象模拟的方法,将DOM的比对操作放在JS层,减少浏览器不必要的重绘,提高效率。
  • 当然有人说虚拟DOM并不比真实的DOM快,其实也是有道理的。当上述table中的每一条数据都改变时,显然真实的DOM操作更快,因为虚拟DOM还存在js中diff算法的比对过程。所以,上述性能优势仅仅适用于大量数据的渲染并且改变的数据只是一小部分的情况。

如下DOM结构:

<ul id="list"><li class="item">Item1</li><li class="item">Item2</li>
</ul>

映射成虚拟DOM就是这样:

{tag: "ul",attrs: {id:&emsp;"list"},children: [{tag: "li",attrs: { className: "item" },children: ["Item1"]}, {tag: "li",attrs: { className: "item" },children: ["Item2"]}]
} 

使用snabbdom实现vdom

这是一个简易的实现vdom功能的库,相比vue、react,对于vdom这块更加简易,适合我们学习vdom。vdom里面有两个核心的api,一个是h函数,一个是patch函数,前者用来生成vdom对象,后者的功能在于做虚拟dom的比对和将vdom挂载到真实DOM上

简单介绍一下这两个函数的用法:

h('标签名', {属性}, [子元素])
h('标签名', {属性}, [文本])
patch(container, vnode) // container为容器DOM元素
patch(vnode, newVnode)

现在我们就来用snabbdom重写一下刚才的例子:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body><div id="container"></div><button id="btn-change">改变</button><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script><script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script><script>let snabbdom = window.snabbdom;// 定义patchlet patch = snabbdom.init([snabbdom_class,snabbdom_props,snabbdom_style,snabbdom_eventlisteners]);//定义hlet h = snabbdom.h;const data = [{name: "张三",age: "20",address: "北京"},{name: "李四",age: "21",address: "武汉"},{name: "王五",age: "22",address: "杭州"},];data.unshift({name: "姓名", age: "年龄", address: "地址"});let container = document.getElementById('container');let vnode;const render = (data) => {let newVnode = h('table', {}, data.map(item => { let tds = [];for(let i in item) {if(item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i] + ''));}}return h('tr', {}, tds);}));if(vnode) {patch(vnode, newVnode);} else {patch(container, newVnode);}vnode = newVnode;}render(data);let btnChnage = document.getElementById('btn-change');btnChnage.addEventListener('click', function() {data[1].age = 30;data[2].address = "深圳";//re-renderrender(data);})</script>
</body>
</html>

你会发现,只有改变的栏目才闪烁,也就是进行重绘,数据没有改变的栏目还是保持原样,这样就大大节省了浏览器重新渲染的开销

vue中使用h函数生成虚拟DOM返回

const vm = new Vue({el: '#app',data: {user: {name:'poetry'}},render(h){// h()// h(App)// h('div',[])let vnode = h('div',{},'hello world');return vnode}
});

Vue中diff算法原理

DOM操作是非常昂贵的,因此我们需要尽量地减少DOM操作。这就需要找出本次DOM必须更新的节点来更新,其他的不更新,这个找出的过程,就需要应用diff算法

vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式+双指针(头尾都加指针)的方式进行比较。

简单来说,Diff算法有以下过程

  • 同级比较,再比较子节点(根据key和tag标签名判断)

  • 先判断一方有子节点和一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)

  • 比较都有子节点的情况(核心diff)

  • 递归比较子节点

  • 正常Diff两个树的时间复杂度是O(n3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

  • Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅

  • 在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升

vue3中采用最长递增子序列来实现diff优化

回答范例

  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
  • vue3中引入的更新策略:静态节点标记等

vdom中diff算法的简易实现
以下代码只是帮助大家理解diff算法的原理和流程

1.将vdom转化为真实dom:

const createElement = (vnode) => {let tag = vnode.tag;let attrs = vnode.attrs || {};let children = vnode.children || [];if(!tag) {return null;}//创建元素let elem = document.createElement(tag);//属性let attrName;for (attrName in attrs) {if(attrs.hasOwnProperty(attrName)) {elem.setAttribute(attrName, attrs[attrName]);}}//子元素children.forEach(childVnode => {//给elem添加子元素elem.appendChild(createElement(childVnode));})//返回真实的dom元素return elem;
}

2.用简易diff算法做更新操作

function updateChildren(vnode, newVnode) {let children = vnode.children || [];let newChildren = newVnode.children || [];children.forEach((childVnode, index) => {let newChildVNode = newChildren[index];if(childVnode.tag === newChildVNode.tag) {//深层次对比, 递归过程updateChildren(childVnode, newChildVNode);} else {//替换replaceNode(childVnode, newChildVNode);}})
}

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

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

相关文章

python通过ssh密钥等形式链接到redis服务器

当使用 RSA 私钥进行 SSH 认证时&#xff0c;你可以通过 paramiko 的 RSAKey 来加载私钥&#xff0c;并用其创建 SSH 隧道。以下是修改后的代码示例&#xff0c;使用 RSA 私钥进行 SSH 连接&#xff1a; 示例代码 import paramiko from paramiko import RSAKey from sshtunne…

iview弹窗提交问题优化

如上图所示 有时候在弹窗中 有比较复杂的表格组件数据 这时候 你如果把提交按钮直接放在弹窗上 就会很麻烦 不仅要处理表格的验证 同时也要维护弹窗的开启和关闭状态 不是很自由 这时候 就看见把提交按钮单独摘出来 可以在自建的按钮上 判断各种状态 是不是很方便呢

智慧城市包括哪些内容?有哪些智慧城市物联网方案?

数字城市、智慧城市的发展&#xff0c;离不开对公共基础设施的数字化、智慧化改造升级。通过融合边缘计算、5G、物联网、数字孪生、人工智能等新一代信息技术&#xff0c;助力传统公共基础设施提升增强全流程数据能力、计算能力、服务能力&#xff0c;从而不断丰富公共基础设施…

Android Audio实战——音频属性设置(二十一)

在 Android 中,使用音频属性(AudioAttributes)可以控制音频的行为。AudioAttributes 已经定义了一些常见的属性,比如音频用途、音频内容类型、音频标志等。 一、音频属性简介 1、常见属性 音量(volume):使用 setParameters("volume=5") 将音量设置为 5(范围…

pymysql的基本用法

** PyMySQL是一个Python的MySQL客户端&#xff0c;可以用来连接MySQL数据库并进行操作。 ** 1、安装PyMySQL 首先需要安装PyMySQL库&#xff0c;可以使用pip命令进行安装&#xff1a; pip install pymysql2、连接数据库 在Python中连接MySQL数据库需要使用PyMySQL模块中的…

iOS--UIPickerView学习

UIPickerView 使用场景和功能UIPickerView遵循代理协议和数据源协议创建对象&#xff0c;添加代理必须实现的代理方法非必要实现的方法demo用到的其他函数提示 效果展示 使用场景和功能 UIPickerView 最常见的用途是作为选项选择器&#xff0c;允许用户从多个选项中选择一个。…

C++11——initializer_list

initializer_list的简介 initializer_list是C11新出的一个类型&#xff0c;正如类型的简介所说&#xff0c;initializer_list一般用于作为构造函数的参数&#xff0c;来让我们更方便赋值 但是光看这些&#xff0c;我们还是不知道initializer_list到底是个什么类型&#xff0c;…

《尚品甄选》:后台系统——分类品牌和规格管理(debug一遍)

文章目录 一、分类品牌管理1.1 表结构介绍1.2 列表查询1.3 添加功能1.4 修改功能1.5 删除功能 二、商品规格管理2.1 表结构介绍2.2 列表查询2.3 添加功能2.4 修改功能2.5 删除功能 一、分类品牌管理 分类品牌管理就是将分类的数据和品牌的数据进行关联&#xff0c;分类数据和品…

HTTP /1.0 /1.1 /2.0 /3.0改变和区别

HTTP&#xff08;Hypertext Transfer Protocol&#xff09;是一种用于传输超文本的协议&#xff0c;它是Web上数据通信的基础。不同版本的HTTP协议有一些重要的变化和改进。以下是HTTP/1.0、HTTP/1.1、HTTP/2.0和HTTP/3.0的主要变化和区别&#xff1a; HTTP/1.0: 1.无连接性&…

Python面经【7】

Python面经【7】 一、存入字典里的数据有没有先后排序二、lambda表达式格式以及应用场景三、如何理解Python中字符中的字符四、介绍一下except的作用和用法五、在 except 中 return 后还会不会执行 finally 中的代码&#xff1f;怎么抛出自定义异常六、read、readline和readlin…

美团YOLOv6量化部署实战方案

文章目录 1. 背景和难点2. 量化方案实战2.1 重参数化优化器2.1.1 RepOpt2.1.2 RepOpt 版本的 PTQ2.1.3 RepOpt 版本的 QAT2.2 基于量化敏感度分析的部分量化2.3 基于通道蒸馏的量化感知训练2.3.1 通道蒸馏2.3.2 YOLOv6 量化感知蒸馏框架3. 部署时优化3.1 图优化3.1.1 性能分析3…

【java】图书管理系统

完整代码链接&#xff1a;https://gitee.com/zeng-xuehui/Java_repository/tree/master/test_11_27_1/src我们在写这个系统时&#xff0c;首先需要搭建框架&#xff0c;再实现业务逻辑&#xff1b;图书管理系统是用户通过各种功能对图书进行操作的一个系统&#xff1b;我们需要…

【腾讯地图】【微信小程序】地图选点

【相关文章】 【腾讯地图】【微信小程序】地图选点 【腾讯地图】【微信小程序】路线规划 【腾讯地图】【微信小程序】城市记录&#xff08;基于地图选点入门版&#xff09; 【效果展示】 【官方文档】 微信小程序插件-地图选点插件 【完善流程】 当前操作和官方文档操作有部…

36 - 电商系统表设计优化案例分析

如果在业务架构设计初期&#xff0c;表结构没有设计好&#xff0c;那么后期随着业务以及数据量的增多&#xff0c;系统就很容易出现瓶颈。如果表结构扩展性差&#xff0c;业务耦合度将会越来越高&#xff0c;系统的复杂度也将随之增加。这一讲我将以电商系统中的表结构设计为例…

vue2+element-ui npm run build打包后,在服务器打开报错

报错 页面的图标也显示不出来&#xff0c;如下 解决&#xff1a; 在build->utils.js文件里面加上publicPath: ../../&#xff0c;再打包发布一下就可以了 // Extract CSS when that option is specified// (which is the case during production build)if (options.extrac…

PCL 判断一个点是否在多边形内部(2D)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 这里实现一种基于点的射线法来判断一个点是否一个多边形的内部,思路其实相对简单,但是很实用。具体内容如下: 首先,我们需要构建一条水平的射线(半无限射线,x增加,但y值不变)。计算它穿过多少条边。在每个与…

【javascript】如何判断一个对象属性是否存在

前言 在javascript里&#xff0c;可以有多种判断对象属性是否存在的方法&#xff0c;使用哪种方法来判断&#xff0c;取决于对 “存在” 两个字的定义是什么。 方法1&#xff1a;对比undefined const obj {} if (obj.id ! undefined) {console.log(存在) } else {console.l…

Spring Cloud Stream如何屏蔽不同MQ带来的差异性?

引言 在当前的微服务架构下&#xff0c;使用消息队列&#xff08;MQ&#xff09;技术是实现服务解耦和削峰填谷的重要策略。为了保证系统的灵活性和可替换性&#xff0c;我们需要避免对单一开源技术的依赖。 市面上有多种消息队列技术&#xff0c;如 Kafka、RocketMQ、Rabbit…

思维模型 达维多定律

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。持续创新&#xff0c;引领市场潮流。 1 达维多定律的应用 1.1 达维多定律应用之吉列公司&#xff1a;不断创新的刀片领导者 吉列公司是一家以剃须刀片而闻名的公司。自 1901 年推出首款安…

【开源视频联动物联网平台】开箱即用的物联网项目介绍

写一个开箱即用的物联网项目捐献给Dromara组织 一、平台简介 MzMedia开源视频联动物联网平台&#xff0c;简单易用&#xff0c;更适合中小企业和个人学习使用。适用于智能家居、农业监测、水利监测、工业控制&#xff0c;车联网&#xff0c;监控直播&#xff0c;慢直播等场景。…