平乡县网站建设海外互联网推广平台
参考文章做的总结,如有不足之处请指正!
在讲虚拟dom之前,先讲讲,为什么前端操作dom会导致页面性能降低?
先说几个概念 有助于后面的理解
什么是 JavaScript 引擎?
JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,
能够将 Javascript 代码处理并执行的运行环境
什么是渲染引擎?
渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。这是每一个浏览器的核心部分,所以渲染引擎也称为浏览器内核。它负责取得网页的内容(HTML、XML、图象等等)、整理信息(例如加入CSS等),以及计算网页的显示方式然后会输出至显示器
浏览器的渲染机制
1、从下载文档到渲染页面的过程中,浏览器会通过解析HTML文档来构建DOM树,解析CSS产生CSS规则树(CSSOM)。
2、渲染过程中,如果遇到<script>就停止渲染,执行JS代码。因为浏览器有GUI渲染线程与JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。 JavaScript的加载、解析与执行会阻塞DOM的构建。
3、之后根据DOM树和CSS规则树构建渲染树(Render-Tree),在这个过程中CSS会根据选择器匹配HTML元素。渲染树包括了每个元素的大小、边距等样式属性,渲染树中不包含设置为 display: none; 的隐藏元素及<head>、<script>等不可见元素(但是对于visibility: hidden;或opacity: 0;的元素,它们会占据屏幕空间,因此它们将出现在渲染树中)。 最后浏览器根据元素的坐标和大小来计算每个元素的位置,并绘制这些元素到页面上。
JavaScript中 js 引擎和渲染引擎(浏览器内核)是独立实现的。使用 js 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流”。每操作一次 DOM,都要跨界一次。跨界的次数一多,就会产生比较明显的性能问题。
在浏览器中,DOM的实现和ECMAScript的实现是分离的。比如在Chrome中使用WebKit中的WebCore处理DOM和渲染,但ECMAScript是在V8引擎中实现的。所以通过JavaScript代码调用DOM接口,相当于两个独立模块的交互。相比较在同一模块中的调用,这种跨模块的调用其性能损耗是很高的。
DOM操作通常会导致浏览器的重绘(repaint)和回流(reflow,也叫重布局),重绘和回流的代价很高。**
重绘指的是页面的某些部分要重新绘制,比如颜色或背景色的修改,元素的位置和尺寸并未改变。
回流则是元素的位置或尺寸发生了改变,浏览器需要重新计算渲染树,导致渲染树的一部分或全部发生变化;渲染树重新建立后,浏览器再重新绘制页面上受影响的元素。
回流的代价比重绘的代价高很多,重绘不一定是因为回流,但回流一定会导致重绘。
浏览器渲染页面流程 参考
JS操作DOM为什么会影响性能?
正是因为这样 才会引入虚拟dom
虚拟DOM的意义就在于使找出差异的性能消耗最小化,直接操作DOM的性能开销是庞大的,
但是即便是使用虚拟DOM,最终还是要改变真实DOM,也就是说必要的DOM操作是不可避免的,
而虚拟DOM则通过它的diff算法使得DOM的更新范围尽可能变小,降低了真实DOM操作的性能开销,
同时通过框架的封装给开发者提供了一种更友好的声明式的前端开发方式
下面举个例子 操作真实dom和虚拟dom的区别:比如 有A、B、C、D四个人。
先让A站第一个,在让B站第二个,C站第三个,但是我最后让D站第一个,然后其他就往后移
(直接操作真实的DOM节点的话,浏览器会一个一个从头到尾执行一边)这样其他三个人之前的排位置的时间算是白花了,还要往旁边挪开,空出一个位置给D。
四个人还好,但是要是四百个人呢,399个人需要移动位置,真的是有点浪费时间和精力了。
所以那有没有更好的办法呢?
这个时候虚拟DOM就显得更加优秀了。虚拟DOM是一个js对象。怎么理解这个js对象呢?
我们都知道对象使用之前要定义,不定义直接使用的话,就会报错。也就是说是用不了。
而这个虚拟DOM呢,就像是一个空对象,操作虚拟DOM就像是网这个对象里面添加属性和对应的属性值。
对象内的属性全部定义好了之后,再按照这个对象里面的内容,全部转化,输送给真实的DOM,让其在页面中渲染出来。因为这个对象不可能一直保持不变,在开发中出现代码的增删改是很正常的现象。
操作的是虚拟DOM,那是所以只要对象一改动,就能马上反应到虚拟DOM上。那又是如何反应的呢?
这里面又牵扯到一个概念就是diff算法。
反应的方式就是通过diff算法来实现的。对象更新时会生成一个新DOM对象,diff算法就是把新的DOM对象和老的DOM对象进行比较,1、发现新属性里没有老属性,那么老属性就直接卸载;新属性直接安装
2、发现新属性的和老属性全部相同(属性值也有可能是对象,也要往下面核对),那么就保留
3、发现同一个属性里,新和老的有些值不同,那么则要排列比较,看哪些修改了,哪些是新增的,哪些是删除的。
这样在一定程度上也比更深的遍历发现同更加节约时间。因为老对象的改变,则会使真实的DOM也会发生改变,这样就在页面上实现了刷新在上面3的内容中,我们又可以得到一个知识点:那就是虚拟DOM的key值。
(Vue2 的模板语法,即被写在 <template> 标签内的所有 HTML 标签,并非直接展示在页面上,而是会先被 Vue 进行解析,生成虚拟DOM节点,之后再由虚拟DOM转为真实DOM,真实DOM才会真正显示在页面上。在虚拟DOM转换的过程中,如何提升解析效率?便是通过 key。)key值就相当于给这些属性值加上了一个标签,这样我们就可以通过这个key值去找到对应的值与之比较或是别的操作。所以,这里我们就要求key值最好是独一无二的。如果用index作为key值得话,如果值得位置有变动,那么对应的index指向的就是变动后的值,所以变动后的值的位置和原来的位置是不是一样,不能确定因此,index作为key值会有可能会存在一定偏差。
看一下页面渲染的流程:解析HTML -> 生成DOM -> 生成 CSSOM -> Layout -> Paint -> Compiler
下面对比一下修改DOM时真实DOM操作和Virtual DOM的过程,来看一下它们重排重绘的性能消耗
真实DOM∶ 生成HTML字符串+重建所有的DOM元素
虚拟DOM∶ 生成vNode+ DOMDiff+必要的dom更新
<!--这是普通的Html标签写法->
<a class="link" href="https://github.com/facebook/react">React<a>
//这是在js中手动生成相同dom的写法
var a = document.createElement('a')
a.setAttribute('class', 'link')
a.setAttribute('href', 'https://github.com/facebook/react')
a.appendChild(document.createTextNode('React'))
//这是一种封装,沿用的React.createElement的命名
var a = React.createElement('a', {className: 'link',href: 'https://github.com/facebook/react'
}, 'React')
React.createElement 的方法名,看似创建了一个Element,实质只是一个轻量级的数据结构,其最简形式如下
var a = {type: 'a',props: {children: 'React',className: 'link',href: 'facebook/react · GitHub'},_isReactElement: true
}React.render(a, document.body)
React.render(ReactElement, DOM) 中所谓的 ReactElement,是指私有属性_isReactElement 为 true 的一种数据结构,而非真正的Element。
所有html结构,都可以用js dom来构造,而且能将构造的步骤封装起来,做到「数据-dom结构」的映射。缓存初始数据,新数据进来时,与旧数据对比,找到差异,根据差异本身的性质进行dom操作;无差异,则不作为。dom本身在js中就是一种数据结构,console.dir(document.body),在控制台可以看到body的数据结构。然而,dom相关的数据丰富而且复杂,我们其实只关心少数元素的少数属性。建立一个javascript plain object,非常轻量,用它保存我们真正关心的与dom相关的少数数据;对它进行操作,然后对比操作前后的差异,再根据映射关系去操作真正的dom,无疑能提高性能。这就是虚拟DOM的理念。
加入虚拟dom之后的浏览器更新
1、将页面改变的内容应用到虚拟 DOM 上,而不是直接应用到 DOM 上;
2、变化被应用到虚拟 DOM 上时,虚拟 DOM 并不急着去渲染页面,而仅仅是调整虚拟 DOM 的内部状态,这样操作虚拟 DOM 的代价就变得非常轻了。
3、在虚拟 DOM 收集到足够的改变时,再把这些变化一次性应用到真实的 DOM 上。
这里参考vue中如何将虚拟dom转为真实dom
超级推荐看这个 小白也能懂的 vue是如何渲染虚拟dom到dom上的
虚拟dom的缺点
1、首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢。代码更多,体积更大
2、内存占用增大
3、 小量的单一的dom修改使用虚拟dom成本反而更高,不如直接修改真实dom快
这篇文章是翻阅资料后得到的总结