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

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. STM32-I2C_CheckEvent-标志位自动清除理解

    STM32里I2C_CheckEvent函数我们应该是相当熟悉了,在每次发送数据后我们都需要检验相应的EVx(x = 0,1,2,,,)事件是否有发送. 函数定义如下: ErrorStatus I2C ...

  2. Kafka集群部署以及使用

    Kafka集群部署 部署步骤 hadoop102 hadoop103 hadoop104 zk zk zk kafka kafka kafka http://kafka.apache.org/down ...

  3. [leetcode] 96 Unique Binary Search Trees (Medium)

    原题 字母题 思路: 一开始妹有一点思路,去查了二叉查找树,发现有个叫做卡特兰数的东西. 1.求可行的二叉查找树的数量,只要满足中序遍历有序. 2.以一个结点为根的可行二叉树数量就是左右子树可行二叉树 ...

  4. 脑裂是什么?Zookeeper是如何解决的?

    什么是脑裂 脑裂(split-brain)就是"大脑分裂",也就是本来一个"大脑"被拆分了两个或多个"大脑",我们都知道,如果一个人有多个大 ...

  5. 【git】Github上面的开源代码怎么在本地编译运行

    最近才发现Github是一个好东西,可以从上面学到很多东西,不说了,赶快写完去学习去... 1.首先你可以看看这个开源项目的README.md,一般一般这里都会有项目的使用方式以及一些注意的点 2.你 ...

  6. kuberenetes CRD开发指南

    扩展kubernetes两个最常用最需要掌握的东西:自定义资源CRD 和 adminsion webhook, 本文教你如何十分钟掌握CRD开发. kubernetes允许用户自定义自己的资源对象,就 ...

  7. 10-Helm

    Helm Chart仓库 helm 架构 https://helm.sh/docs/architecture/ 主要概念 chart 创建Kubernetes应用程序实例所必需的一组信息 config ...

  8. 最新try2hack全详解

    第一题http://www.try2hack.nl/levels/: 方法:直接右键看网页源码 第二题http://www.try2hack.nl/levels/level2-xfdgnh.xhtml ...

  9. 洛谷 P3387 题解

    题面 裸跑一遍SPFA,统计每个点的入队次数: 如果该点的入队次数>=总点数,那么该点便是一个负环上的点: 重点!!!: 1.不是“YES”,是“YE5”: 2.不是“NO”,是“N0”:(是零 ...

  10. Spring中FactoryBean的作用和实现原理

    BeanFactory与FactoryBean,相信很多刚翻看Spring源码的同学跟我一样很好奇这俩货怎么长得这么像,分别都是干啥用的.BeanFactory是Spring中Bean工厂的顶层接口, ...