为什么是虚拟DOM

引言

作为前端开发者,无论是否使用过Vue框架作为技术栈,都应该听过说虚拟DOM这个概念,也称VDom。
虚拟DOM最先是由facebook(现在叫Meta)技术团队提出的,最先运用在React框架中,之后Vue2.0中引入了虚拟DOM的概念。
虚拟 DOM 加持下的MVVM框架页面呈现:
为什么是虚拟DOM_第1张图片

进入VDom之前,我们先来了解下JavaScript操作DOM的方式和性能差异;

JS操作DOM的方法和性能

JavaScript操作DOM一般采用以下两种方式:

  • 通过原生的JavaScript代码直接操作DOM
  • 通过html字符串拼接+innerHTML直接操作DOM

原生操作

在我们前端开发者最初接触JavaScript时,都将学习如何使用原生js对DOM进行操作(增、删、改、查、事件绑定等等)。
例如有如下代码:

<div id="testEle">这是div的文本内容div>
// 修改div的内容
const ele = documont.querySelector('#testEle');
div.innerText = "使用js-innerText修改dom文本"
// or
div.textContent = "使用js-textContent修改dom文本"

innerHtml

使用innerHTML方式

const html = `
使用innerHTML
`
div.innerHtml = html;

虚拟DOM

这里先解释一下什么是虚拟DOM,其实虚拟DOM就是一段用来描述真实的DOM的JavaScript代码(对象)并能根据一定的规则转换成真实的DOM节点
使用虚拟DOM操作html

const virtualDOM = {
  tag: 'div',
  children: [{ children: '使用虚拟DOM操作html' }]
}
// render 函数将虚拟 DOM 创建为真实 DOM ,并将其插入到文档中
render(virtualDOM)

以上便是三种操作DOM的方式,在写法上有些不同,下面重点分析一下性能问题;

性能差异对比

首先有两个要点需要声明:

  • js的操作性能要远远大于dom的操作,他们两个不是一个量级;
  • js原生操作dom的方法应该除去innerHTML方法,因为innerHTML较为复杂;

最快的性能

修改dom最直接的方式就是用原生的方法,也是最优的性能选择;

div.textContent = "使用js-textContent修改dom文本"

因为我们知道dom结构中哪个地方需要修改,要修改成什么内容,直接使用js操作dom就是最优的

虚拟DOM的性能

使用虚拟DOM的方式去操作DOM,其实就是找出来虚拟DOM前后的区别,然后更新一下,请看下面示例代码:


<div>虚拟DOM修改前div>

<div>虚拟DOM修改后div>

这里需要说一下,虚拟DOM的底层还是使用的原生的js去操作的DOM,只不过我们对修改做了一层封装。所以虚拟DOM的性能要低于原生js操作DOM的方式;
那为什么在这里需要额外加一层封装呢?
首先,我们要知道在前端性能中最大的就是频繁操作DOM,频繁变动DOM会造成浏览器的回流与重回,因此这一层抽象,在“频繁操作”过程中尽可能地将DOM差异计算得到最终结果,以确保DOM不会出过度操作导致性能消耗,比如DOM变更中出现A-B-C-A-D情况,在VDom中直接操作A-D情况,极大减少了中间过程的浪费。从框架角度看,中间层对Dom操作的抽象,可以解放开发者针对DOM的手动操作,同时在适配更多“中间层”方面可以得到更多平台支持,实现框架的跨平台底层支撑。

通过以上案例我们可以得出的结论是:

  • 原生js操作DOM的性能 = js操作DOM的性能;
  • 虚拟DOM的性能 = js找出DOM前后差异的性能 + js操作DOM的性能;

根据上面的公式可知,只有当js找出DOM前后差异的性能消耗为0时,两者的性能才会相等,但是永远不会超过原生js操作DOM的性能,所以我们在框架中使用算法去对比虚拟DOM的差异时,就是无限优化并使其的性能消耗降到最低(这也是后面为什么使用diff算法的起因)
至于为什么还要使用虚拟DOM,这个问题在文章的最后再说。

innerHTML的性能

innerHTML不是简单的赋值,页面要想渲染出来html文档的内容,就先要将innerHTML的内容解析成DOM结构树,然后再插入到DOM文档结构中去,当然插入前需要先删除旧的文档结构。这里面有几个关键问题:

  • 解析DOM结构树
  • 删除原始DOM结构
  • 创建新的DOM结构

以上三个关键点其中解析DOM结构树是属于js级别的计算,另外后面两个则属于DOM级别的操作计算。

三者对比

现在将三者的性能消耗进行一个对比分析:

操作方式 JS级别 DOM级别
原生JS 纯JS运算 DOM运算
innerHTML 纯JS运算html字符串DOM解析 DOM创建
虚拟DOM 创建虚拟DOM DOM创建

上面表格对比了创建一个新的页面的性能消耗对比,先抛开原生JS的方式(因为该方式上文已经说了,是最优的)仅对比虚拟DOMinnerHTML的性能损耗差异;

  • innerHTML的性能损耗 = js运算html字符串拼接性能损耗 + DOM创建的性能损耗;
  • 虚拟DOM的性能损耗 = js创建虚拟DOM对象的性能损耗 + DOM创建的性能损耗;

单从创建页面这个维度对比,两者貌似性能差异没有太大的区别;
下面我们从另外一个维度来进行对一下:更新维度
使用innerHTML更新页面的过程是重新构建html字符串,再重新设置DOM元素的innerHTM属性,这其实是在说,哪怕我们只更改了一个文字,也要重新设置innerHTML属性。而重新设置innerHTML属性就等价于销毁所有旧的DOM元素,再全量创建新的DOM元素。
再来看虚拟DOM是如何更新页面的。它需要重新创建JavaScript对象(虚拟DOM),然后比较新旧虚拟DOM,找到变化的元素并更新它。
另外一个因素是和页面的代码体量有关系。对于innerHTML来说,页面大小越大,更新的时候性能消耗也就越大。而虚拟DOM紧紧需要更新变动的部分,这就与需要更新的数据量有关,而与页面大小没有关系。所以这个结论就是页面越大,innerHTML的性能消耗就越大,远远超过虚拟DOM的性能消耗。

操作方式 JS级别 DOM级别
原生JS 纯JS运算 DOM运算
innerHTML 纯JS运算html字符串DOM解析 销毁DOM+DOM创建,与页面大小有关
虚拟DOM 创建虚拟DOM+Diff算法 变化部分的DOM更新,与更新的数据量有关

根据上面的性能消耗分析对比可以知道:原生JS < 虚拟DOM < innerHTML。那么既然如此为什么我们不使用原生JS呢,这就牵出另外一个重要的问题可维护性

可维护性

原生的JS操作是性能最优的,但是在实践的业务项目开发中,我们很少使用原生的JS直接去开发,之前是使用JQuery,现在是使用AngularVueReact等等。这主要是因为减少开发者的心智负担,方便快速开发与代码维护。

命令式

原生的JS代码一般都是命令式的,就是我想做什么操作,我就发出什么指令,关注的是过程。一个典型的命令式框架(库)是JQuery,例如我想修改一个标签中的文本:

$('#divTag').text('修改后的文案')

命令式代码基本上就是一条条指令,在告诉程序我要干什么吗。如果我又想修改一下className呢?

$('#divTag').className('add-class')

声明式

声明式更关注的是结果,我们不在乎实现的过程是什么,我们只需要告诉框架,我们想要的结果,然后帮我来实现就行。同样的代码我们使用声明式来实现一下。

<div id="divTag" class="add-class">修改后的文案div>

好了,这就实现了我们想要的结果,是不是可读性很好。至于他是如何实现的这个结果,我们一般并不需要关心,不过这里还是需要说明一下,Vue底层还是使用的命令式的代码帮我们做的封装,毕竟命令式的性能是最优的。

总结

在Vue项目开发时,推荐使用模版语法,因为模版语法比直接使用虚拟DOM更加直观(虚拟DOM比模版语法更加灵活)。模版语法需要通过编译器转换成虚拟DOM,再使用渲染器渲染成真实的DOM节点,而虚拟DOM较传统的innerHTML最大的优势是数据更新节段,它紧紧更新变化的部分,性能消耗更小。在权衡了性能消耗、代码的可维护性,Vue(包括React等)主流框架,使用了虚拟DOM这个概念。当然这也仅仅是其中的一部分使用的理由,因为还有响应式等等。

你可能感兴趣的:(javascript,前端,html)