面试官经常会问你:“平时工作中,你怎么优化自己应用的性能?”
你回答如下:“我平时遵循以下几条原则来优化我的项目、以提高性能,主要有:”
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步过程:
- 解析HTML,构建DOM树(DOM Tree)
- 解析CSS,生成CSS规则树(CSSOM Tree)
- 合并DOM树和CSS规则树,生成渲染树render树(render Tree)
- 布局render树,根据生成的render树来对各元素尺寸、位置进行计算,得到每个节点的几何信息。(根据视口的大小来计算元素的位置和大小)(重排会走这一步)
- 绘制render树,绘制页面像素信息(根据render树上每个节点的几何信息,得到每个节点的像素数)(重绘会走这一步)
- 浏览器会将各层节点的像素信息发送给GPU,GPU将各层合成、绘制展示到页面上
4-1、浏览器渲染引擎是如何生成渲染树(render Tree)的?
先看一张图:
由上图得知如下流程:
- 从DOM Tree的根节点开始遍历每一个可见节点(除meta、link、script等这些标签;除display:none;的元素)
- 对于每个可见节点,在CSSOM中找到对应规则并将样式规则应用到对应节点上。
- 根据每一个可见节点,以及其对应的样式,组合生成渲染树。
不可见节点: 不会渲染输出的节点(不会显示在屏幕上的节点)有以下几种
- meta、link、script等标签;
- 通过css进行隐藏的节点,即display:none;(opacity对人类不可见,计算机还能看见,所以还会渲染。)(那visibility为隐藏的元素会不会被渲染呢?做个试验,一个div设置visibility不可见,左浮动,周围全是文字,看文字环绕是否让出一块空白区域。最后试验证明确实绕出了一段空白的位置,说明visibility和opacity设置的不可见只是对人类肉眼不可见,计算机还是会在生成render Tree的时候计算位置信息并把他绘制出来。试验结果如下图:)
5、什么是浏览器渲染引擎的重排和重绘?
5-1、重排
当DOM的变化影响了元素的几何属性(宽和高),浏览器需要重新计算元素的几何属性,同样其他相邻元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为“重排”。
换句话说,改变了页面中某元素的位置、尺寸大小,进而也就改变了他的占地面积。那这个元素修改了占地面积后,其后紧邻的元素就得挪动位置。给她让地儿(或者向前赶赶)。紧邻的元素挪动了,那紧邻元素后边的元素也会连锁效应式的修改。这就好比一排人排队。前边的人突然变胖了、变瘦了、向前挪了、向后挤了、都会导致队伍中后边的人也跟随之改变位置,由此导致一连串的人都挪动位置。这时浏览器就要重新排版各个受到影响的元素的位置。反应在渲染引擎的工作流程中也就是浏览器需要重新计算元素位置信息并布局render树。这就是重排。
5-2、重绘
完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。
因为重排在重绘的上一步,所以重排发生后自然会导致重绘。这个很好理解。
6、什么时候会引发重排?
当页面布局和几何属性改变时就需要重排:
(核心就是:只要某个属性能导致位置信息发生改变,就会触发重排 )
- 添加或删除可见的DOM元素。(一堆人排队,添加即中间插入了一个人/删除即中间一个人走了,势必会影响后边排队的人的位置信息也发生改变)
- 元素位置改变(重排就是因为位置信息改变了)
- 元素尺寸改变( 外边距、内边距、边框厚度、宽度、高度等)
- 内容改变,例:文本数量/内容改变、或图片被另一个不同尺寸的图片替代、字体大小改变、(文字加粗?)导致DOM元素位置、面积改变。【计算会消耗CPU的能力】
- 页面渲染器初始化(这算重走流程吧,肯定要重排)
- 浏览器窗口尺寸改变(位置信息会被迫调整,发生重排。见下图的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、什么时候会引发重绘?
- 重排必然引发重绘,这是肯定的。因为浏览器的工作流程就是排版后渲染。重排会回流(回滚流程)到排版阶段,排版后需要重新绘制页面。
- 单独触发重绘的情况:
除元素尺寸、位置发生改变以外的情况,(比如字体颜色、背景色等发生改变)。(我怀疑文字加粗也会触发重排,但是我没有证据。理论上来说如果在一个固定尺寸的div内加粗文字,应该不会影响后边元素的重排,但可能该div内部的其他相邻文字或元素会发生重排。)
试验gif图:
(想到一个验证只发生重绘的情况,那就是后边也加点元素,如果重排了,后边的元素在控制台的检测下也会闪绿光。)
9、为什么不提倡重排和重绘?
既然知道了这个dom操作会触发重排、重绘。那又是为什么要尽量避免重排和重绘呢?
换句话说,重排和重绘的副作用是什么?缺点是什么?
这就要引入CPU和GPU了。
重排会占用CPU,dom元素位置计算会消耗CPU的算力,所以应该尽量减少CPU的占用,使电脑不卡顿。
重绘会占用GPU,渲染页面时会消耗GPU的算力。
GPU的分类:
- 家用GPU
适合做贴图、特效、光影等效果。不适合画图形。 - 专业GPU
适合画图形。不适合做贴图、特效、光影等效果。
DOM操作基本就是画图形的,但浏览器中用的就是家用GPU,其画图形耗费的性能是专业GPU的几十倍。所以不提倡频繁用装有家用GPU的浏览器绘制页面。也就是不提倡频繁触发重绘。
10、总结: 为什么操作DOM非常昂贵?
- ES和 DOM是两种东西,每次连接都需要消耗性能
- 操作DOM会导致重排和重绘,重排会占用、消耗CPU; 重绘会占用、消耗GPU
11、控制台观察一个页面的重排和重绘现象
因为重排必然会引发重绘,所以在浏览器的开发者工具中提供了一个检测重绘的按钮。寻找和打开步骤如下图:
各css属性对重排重绘的影响:https://csstriggers.com/
可以关注我的微信公众号看更多总结文章~