面试官经常会问你:“平时工作中,你怎么优化自己应用的性能?”
你回答如下:“我平时遵循以下几条原则来优化我的项目、以提高性能,主要有:”

a. 减少DOM操作的次数(减少DOM的获取与修改次数)

b. 减少网络请求

c. 压缩、合并静态资源文件(css、js、img等)

d. 小图片文件base64化处理

e. js少用全局变量

f. ...

Bingo!此时,你给自己抛了个可以把自己埋住的大坑。
因为面试官可能会追问你:“为什么减少DOM操作可以提高性能?”

为什么呢?
_______

1、dom是什么?ES和 DOM是什么关系?

DOM就是Document Object Model,文档对象模型,里边是接口,即方法函数。我们通过调用并传指定参数来使用。
官方定义:DOM是一个独立于语言的、用于操作XML和HTML文档的程序接口(API)。在浏览器中主要用于与HTML文档打交道,并且使用DOM API用来访问文档中的数据。
DOM是个与ES语言无关的API,它在浏览器中的接口却是用JavaScript来实现的,DOM就成了现在JS编码中的重要部分。

1-1、各大浏览器中,DOM的位置和JavaScript的位置(渲染引擎与JS引擎相互独立)

浏览器 JS位置 DOM位置
IE JavaScript的实现名为JScript,位于jscript.dll文件中 DOM的实现则存在另一个库中,名为mshtml.dll(内部称为trident)
safari JavaScript部分是由独立的SquirelFish引擎来实现。 DOM和渲染是使用webkit中的webcore实现
google chrome JavaScript引擎是他们自己研发的,名为V8。 使用webkit中的webCore库来渲染页面
firefox JavaScript引擎名为TraceMonkey 渲染引擎Gecko

1-2、ES和 DOM是两种东西

ES通多DOM接口来获取文档中的元素。
正因为浏览器中通常把DOM和ECMAScript独立实现。使得二者相互独立,就像两座孤岛。
所以ES每次操作DOM时,ES和DOM之间就像两个桥之间需要过车辆。
每次链接就都需要搭建一个桥梁,搭桥还是小事,ES请求DOM的车辆过桥时,会经过一个收费站,每次都会被收费。JS引擎会消耗浏览器的性能进行缴费。
而车辆通过后桥就销毁,下次链接重新搭桥二次缴费。所以说JS与DOM每次连接都需要消耗性能
也正因此,有了每操作一次DOM就多做点事的理念,尽可能以最少的次数处理最多的DOM操作,以实现每过一次桥多拉点货的效果。
(VUE也正是这种理念,操作虚拟dom减少性能消耗,因此vue性能更优,另个话题来说。)

2、ES每次访问DOM都需要消耗性能:

正因为二者相互独立,所以每次链接、每次访问DOM都会消耗性能!! 可以说操作dom是十分昂贵的!!宁可处理一万次js,也不操作一次dom!!

3、ES每次修改DOM元素的代价则更为昂贵

像上边说的,每次操作DOM之前,就会先访问DOM,所以也会消耗性能。
在此基础上,因为修改DOM会导致浏览器重新计算页面的几何变化、引发浏览器模板引擎的重排(回流 - 回滚流程)和重绘,进而更加消耗性能。

4、浏览器渲染引擎的工作原理、工作流程是什么?

浏览器下载完页面中的所有资源(比如HTML、JavaScript、CSS、图片等)后,会发生如下的6步过程:

  1. 解析HTML,构建DOM树(DOM Tree)
  2. 解析CSS,生成CSS规则树(CSSOM Tree)
  3. 合并DOM树和CSS规则树,生成渲染树render树(render Tree)
  4. 布局render树,根据生成的render树来对各元素尺寸、位置进行计算,得到每个节点的几何信息。(根据视口的大小来计算元素的位置和大小)(重排会走这一步)
  5. 绘制render树,绘制页面像素信息(根据render树上每个节点的几何信息,得到每个节点的像素数)(重绘会走这一步)
  6. 浏览器会将各层节点的像素信息发送给GPU,GPU将各层合成、绘制展示到页面上

4-1、浏览器渲染引擎是如何生成渲染树(render Tree)的?

先看一张图:

由上图得知如下流程:

  1. 从DOM Tree的根节点开始遍历每一个可见节点(除meta、link、script等这些标签;除display:none;的元素)
  2. 对于每个可见节点,在CSSOM中找到对应规则并将样式规则应用到对应节点上。
  3. 根据每一个可见节点,以及其对应的样式,组合生成渲染树。

不可见节点: 不会渲染输出的节点(不会显示在屏幕上的节点)有以下几种

  • meta、link、script等标签;
  • 通过css进行隐藏的节点,即display:none;(opacity对人类不可见,计算机还能看见,所以还会渲染。)(那visibility为隐藏的元素会不会被渲染呢?做个试验,一个div设置visibility不可见,左浮动,周围全是文字,看文字环绕是否让出一块空白区域。最后试验证明确实绕出了一段空白的位置,说明visibility和opacity设置的不可见只是对人类肉眼不可见,计算机还是会在生成render Tree的时候计算位置信息并把他绘制出来。试验结果如下图:)

5、什么是浏览器渲染引擎的重排和重绘?

5-1、重排

当DOM的变化影响了元素的几何属性(宽和高),浏览器需要重新计算元素的几何属性,同样其他相邻元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为“重排”。

换句话说,改变了页面中某元素的位置、尺寸大小,进而也就改变了他的占地面积。那这个元素修改了占地面积后,其后紧邻的元素就得挪动位置。给她让地儿(或者向前赶赶)。紧邻的元素挪动了,那紧邻元素后边的元素也会连锁效应式的修改。这就好比一排人排队。前边的人突然变胖了、变瘦了、向前挪了、向后挤了、都会导致队伍中后边的人也跟随之改变位置,由此导致一连串的人都挪动位置。这时浏览器就要重新排版各个受到影响的元素的位置。反应在渲染引擎的工作流程中也就是浏览器需要重新计算元素位置信息并布局render树。这就是重排

5-2、重绘

完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘
因为重排在重绘的上一步,所以重排发生后自然会导致重绘。这个很好理解。

6、什么时候会引发重排?

当页面布局和几何属性改变时就需要重排:
(核心就是:只要某个属性能导致位置信息发生改变,就会触发重排 )

  1. 添加或删除可见的DOM元素。(一堆人排队,添加即中间插入了一个人/删除即中间一个人走了,势必会影响后边排队的人的位置信息也发生改变)
  2. 元素位置改变(重排就是因为位置信息改变了)
  3. 元素尺寸改变( 外边距、内边距、边框厚度、宽度、高度等)
  4. 内容改变,例:文本数量/内容改变、或图片被另一个不同尺寸的图片替代、字体大小改变、(文字加粗?)导致DOM元素位置、面积改变。【计算会消耗CPU的能力】
  5. 页面渲染器初始化(这算重走流程吧,肯定要重排)
  6. 浏览器窗口尺寸改变(位置信息会被迫调整,发生重排。见下图的gif图,一个页面中div元素的位置不受视口调整而修改,也会引发重排)【消耗GPU的计算能力】

试验:resize视口,一个页面中div元素的位置不受视口调整而修改,也会引发重排

7、打断浏览器的优化步骤

现代浏览器是相当完善的了,因为多次操作DOM会触发重排重绘、消耗性能。所以除了我们人为的、有意识的去控制操作DOM次数以外,浏览器在设计上进行了优化,也会智能的“节流”操作DOM,比如实现队列化修改、批量执行。

解释来说就是,浏览器会有一个“队列”,用以存放(攒着)需要操作DOM的js程序。每当执行一次js操作dom的代码,这个队列里就先暂存一个程序。等到一段时间后,浏览器再集中、批量的链接一次"ES岛"和"DOM岛"(就是让JS引擎去链接渲染引擎),进而触发一次DOM操作。你可以形象的理解为“过一段时间发一班车”。

但是我们人类感知不到啊,可能会因为误操作打断浏览器的“节流”步骤。迫使浏览器中断当前的“等待”,去赶紧、立马进行一次dom操作。让浏览器赶紧执行完他攒在“队列”里的JS操作DOM的程序后返回最新的DOM位置信息给我们。这就好像电梯门定时自动关闭,但是你却手动按了关门按钮强迫关门一样。

这种情况就发生在我们获取DOM信息的时候:

打断浏览器优化,强迫触发重排的属性:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()

因为要跟浏览器请求最新的DOM信息,所以浏览器就得赶紧让JS引擎去渲染引擎那里进行一次DOM操作。

8、什么时候会引发重绘?

  1. 重排必然引发重绘,这是肯定的。因为浏览器的工作流程就是排版后渲染。重排会回流(回滚流程)到排版阶段,排版后需要重新绘制页面。
  2. 单独触发重绘的情况:
    除元素尺寸、位置发生改变以外的情况,(比如字体颜色、背景色等发生改变)。(我怀疑文字加粗也会触发重排,但是我没有证据。理论上来说如果在一个固定尺寸的div内加粗文字,应该不会影响后边元素的重排,但可能该div内部的其他相邻文字或元素会发生重排。)

试验gif图:

(想到一个验证只发生重绘的情况,那就是后边也加点元素,如果重排了,后边的元素在控制台的检测下也会闪绿光。)

9、为什么不提倡重排和重绘?

既然知道了这个dom操作会触发重排、重绘。那又是为什么要尽量避免重排和重绘呢?
换句话说,重排和重绘的副作用是什么?缺点是什么?

这就要引入CPU和GPU了。

重排会占用CPU,dom元素位置计算会消耗CPU的算力,所以应该尽量减少CPU的占用,使电脑不卡顿。
重绘会占用GPU,渲染页面时会消耗GPU的算力。

GPU的分类:

  1. 家用GPU
    适合做贴图、特效、光影等效果。不适合画图形。
  2. 专业GPU
    适合画图形。不适合做贴图、特效、光影等效果。

DOM操作基本就是画图形的,但浏览器中用的就是家用GPU,其画图形耗费的性能是专业GPU的几十倍。所以不提倡频繁用装有家用GPU的浏览器绘制页面。也就是不提倡频繁触发重绘。

10、总结: 为什么操作DOM非常昂贵?

  1. ES和 DOM是两种东西,每次连接都需要消耗性能
  2. 操作DOM会导致重排和重绘,重排会占用、消耗CPU; 重绘会占用、消耗GPU

11、控制台观察一个页面的重排和重绘现象

因为重排必然会引发重绘,所以在浏览器的开发者工具中提供了一个检测重绘的按钮。寻找和打开步骤如下图:

各css属性对重排重绘的影响:https://csstriggers.com/

为什么操作DOM会影响WEB应用的性能?的更多相关文章

  1. 操作DOM会影响WEB应用的性能

    平时在工作中,要优化自己开发的WEB应用的性能,一般是遵循以下几个原则: 1.减少网络请求. 2.压缩.合并静态资源文件,以此来减轻网络传输的带宽压力和资源消耗. 3.代码逻辑层面上的性能优化.比如减 ...

  2. 操作dom影响性能的原因

    为什么dom操作会影响性能? 在浏览器当中,dom的实现和ECMAScript的实现是分离的. 例如,在IE中,ECMAScrit的实现在jscript.dll中,而DOM的实现在mshtml.dll ...

  3. web进阶之jQuery操作DOM元素&&MySQL记录操作&&PHP面向对象学习笔记

    hi 保持学习数量和质量 1.jQuery操作DOM元素 ----使用attr()方法控制元素的属性 attr()方法的作用是设置或者返回元素的属性,其中attr(属性名)格式是获取元素属性名的值,a ...

  4. JavaScript操作DOM的那些坑

    js在操作DOM中存在着许多跨浏览器方面的坑,本文花了我将近一周的时间整理,我将根据实例整理那些大大小小的“坑”. DOM的工作模式是:先加载文档的静态内容.再以动态方式对它们进行刷新,动态刷新不影响 ...

  5. mui项目中如何使用原生JavaScript代替jquery来操作dom 转自【B5教程网】:http://www.bcty365.com/content-146-3661-1.html

    最近在用mui写页面,当然了在移动App里引入jq或zepto这些框架,肯定是极不理性的.原生JS挺简单,为何需要jq?jq的成功当时是因为ie6.7.8.9.10.chrome.ff这些浏览器不兼容 ...

  6. HTML(.js) – 最简单的方式操作 DOM 的 JS 库

    HTML(.js) 是一个轻量的(压缩后~2kb) JavaScript 库,简化了与 DOM 交互的方法. 这个 JavaScript 库的方法可读性很好,并具有搜索和遍历 DOM 的方法.相比 j ...

  7. 使用原生 JavaScript 操作 DOM

    原文:https://www.sitepoint.com/dom-manipulation-vanilla-javascript-no-jquery/ 微软官方放弃了 IE10-,所以现在可以放心使用 ...

  8. mui项目中如何使用原生JavaScript代替jquery来操作dom

    最近在用mui写页面,当然了在移动App里引入jq或zepto这些框架,肯定是极不理性的.原生JS挺简单,为何需要jq?jq的成功当时是因为ie6.7.8.9.10.chrome.ff这些浏览器不兼容 ...

  9. Angular开发实践(七): 跨平台操作DOM及渲染器Renderer2

    在<Angular开发实践(六):服务端渲染>这篇文章的最后,我们也提到了在服务端渲染中需要牢记的几件事件,其中就包括不要使用window. document. navigator等浏览器 ...

随机推荐

  1. 注入攻击-XSS攻击-CSRF攻击

    1.注入攻击 注入攻击包括系统命令注入,SQL注入,NoSQL注入,ORM注入等 1.1攻击原理 在编写SQL语句时,如果直接将用户传入的数据作为参数使用字符串拼接的方式插入到SQL查询中,那么攻击者 ...

  2. https://www.cnblogs.com/M-LittleBird/p/5902850.html

    https://www.cnblogs.com/M-LittleBird/p/5902850.html

  3. 洛谷P4995 跳跳!题解

    求关注,求赞,求评论QAQ 题目:https://www.luogu.org/problemnew/show/P4995 简单描述一下吧,就是说有n块石头,起始可以跳到任何一块上面,接着也是,只不过每 ...

  4. idea的安装与配置及基本用法

    Intellij IDEA 确实使用更加方便,由于目前只用到maven项目,所以此处只记录maven项目的配置. 一.配置idea前准备: 1.下载idea安装包.jdk安装包.maven安装包.gi ...

  5. vue使用问题总结(长期更新)

    循环中绑定标签的属性 <div class="imgdiv" v-for="template of templateArr"> <img :s ...

  6. Spring集成Shiro使用小结

    shiro的认证流程 Application Code:应用程序代码,由开发人员负责开发的 Subject:框架提供的接口,代表当前用户对象 SecurityManager:框架提供的接口,代表安全管 ...

  7. Android 设置ImageView全屏

    Android 设置ImageView全屏代码如下: <ImageView android:id="@+id/iv_image" android:scaleType=&quo ...

  8. Cesium 学习(二)所支持的模型数据类型,以及转换

    1.Cesium所支持的模型数据类型 目前所知的有glTF.glb.bgltf等格式的模型数据: 想要了解glTF等的知识可以看一下https://www.cnblogs.com/fuckgiser/ ...

  9. spark 源码分析之八--Spark RPC剖析之TransportContext和TransportClientFactory剖析

    spark 源码分析之八--Spark RPC剖析之TransportContext和TransportClientFactory剖析 TransportContext 首先官方文档对Transpor ...

  10. 深入理解Java中的锁(三)

    ReadWriteLock接口 读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作.读锁可以由多个线程同时持有,又称共享锁.写锁同一时间只能由一个线程持有,又称互斥锁.同一时间,两把锁不能被不同 ...