从浏览器渲染讲性能优化

12 阅读7分钟

进程与线程

  1. 进程: 一个程序,浏览器打开一个页面就开辟一个进程。是资源分配的最小单位。
  2. 线程:程序中具体“干活”的人,浏览器具备很多线程。是CPU调度的最小单位。
  3. 一个进程中由一个或多个线程组成,同个进程内的线程之间共享进程的数据。进程之间的内容互相隔离,数据是不能直接共享的。这样就算一个进程挂起或者崩溃也不会影响影响其他进程。
  4. js是单线程执行的。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
  5. 打开一个网页,最少需要四个进程:浏览器主进程(负责界面显示、用户交互、子进程管理等)、网络进程(负责网络资源的加载)、GPU进程(UI界面的绘制)、渲染进程(将 HTML、CSS 和 JavaScript转换成网页)。

浏览器的渲染进程有哪些?

  1. GUI渲染线程:负责渲染页面,解析html、css,构建html树、css树、渲染树和绘制页面。
  2. js引擎线程:负责处理js脚本,解析、运行js代码。一个tab页只有一个js引擎在运行js代码。js引擎线程与渲染线程是互斥的,所以js执行时间过长会导致页面渲染不连贯。
  3. 事件触发线程:事件触发线程监听事件是否触发,当js引擎执行代码(如setTimeout、鼠标点击、异步请求)会将对应的任务加入到事件触发线程中,当对应的事件符合触发条件时,线程会把事件添加到待处理队列的队尾,等待js引擎处理。
  4. 定时器触发线程:是setIntervalsetTimeout所在线程,浏览器的定时计数是由单独的定时器触发线程计数。计时完毕后会添加到事件的队列中,所以任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中。
  5. 异步http请求线程:是XMLHttpRequest连接后新开的线程,检测到状态变更时,如果设置有回调函数,异步线程就将回调函数放入事件队列中,等待JS引擎空闲后执行;

浏览器的渲染过程?

  1. DOM树:渲染进程将html文件内容转成浏览器可以读懂的DOM树结构,GUI渲染线程会自上而下,一行行的渲染解析页面中的代码;当渲染到底部的时候,浏览器已经规划出当前页面的结构和层级嵌套关系;
  2. CSS树:渲染进程将CSS文件解析为浏览器可以看懂的StyleSheet,生成CSS树;
  3. Render树:CSSOM 与 DOM 结合,得到的就是渲染树;
  4. Layout布局:根据视口大小,计算每个节点在视口的具体位置以及大小;
  5. Layer树:为特定的节点生成专用图层,生成图层树;
  6. painting绘制:按照所有解析出来的规则,对每一个图层进行绘制。

加载阶段优化,减少关键资源的大小个数

CSS阻塞文档解析
  1. 样式渲染过程:① 遇到<link>(外链式):会开启一个新的HTTP线程去服务器获取样式代码,而GUI渲染线程会继续向下渲染。也就是CSS样式代码的渲染,一般都发生在DOM TREE生成之后;② 遇到@import(导入式):开启一个新的HTTP线程去服务器获取样式代码,但是会阻碍GUI渲染; ③ 遇到<style>(内嵌式):会等到DOM TREE生成完,外链式获取的样式代码也都拿到了,然后按照编写的先后顺序,依次渲染解析样式代码,以此保证CSS优先级正确。
  2. 优化1:导入外部样式使用<link>@import会阻碍GUI的渲染,不用@import
  3. 优化2:减少HTTP请求次数,把CSS都写入到一个样式表中,只请求一次即可;
  4. 优化3:把<link>放在HEAD中,这样保证样式资源的提前获取。
JS阻塞
  1. 遇到<script>会开启新的HTTP线程去获取JS代码,同时阻碍了GUI的渲染。js的加载、解析运行都会影响文档解析,在构建DOM时遇到JS会暂时解析,等JS引擎运行完毕,再继续解析文档。
  2. 优化1:把<script>获取资源改为异步编程,通过asyncdefer来标记代码。在JS文件没有操作DOM的相关代码,就可以把脚本设置为异步加载,不会阻塞DOM解析。如果JS之间的没有相互依赖,先回来执行谁则使用async即可;需要根据导入的先后顺序去执行则要用defer;

a. <script async>:GUI渲染中遇到<script async>,开启HTTP线程去获取JS代码;获取的过程中GUI继续向下渲染。但是获取到了之后,立即暂停GUI,先把获取的JS先执行,执行完GUI继续渲染;
b. <script defer>:GUI渲染中遇到<script defer>,开启HTTP线程去获取JS代码;获取的过程中GUI继续向下渲染。获取到了之后也要等GUI渲染完,而且所有设置defer的JS都获取完,按照编写的先后顺序,依次渲染解析JS。

  1. 优化2:有一个预解析线程,用来分析HTML文件中的JS、CSS文件,解析到相关的文件后,预解析线程会提前下载这些文件。
图片优化
  1. 请求图片资源不会阻碍GUI渲染,但每一次请求都需要一个HTTP线程。而浏览器可同时开辟的HTTP有数量限制,所以最开始就加载真实图片,可能会导致其它类型的资源获取延后,所以可以进行图片的懒加载。
  2. 使用“BASE64”:要加快图片的渲染,可以跳过获取资源、编码这两步,直接让浏览器绘制即可;小图片一般可以BASE64。

交互阶段优化,减少重排重绘

  1. 重排:对DOM的操作引发DOM的几何位置变化,如修改宽高或隐藏元素等,那么就会重新从布局树开始执行。引发重排操作:添加或删除可见的DOM;元素尺寸的改变(边距、填充、边框、宽度和高度);
  2. 重绘:修改DOM的样式,没有影响其几何属性,浏览器从绘制页面开始。引发重绘操作:比如修改了颜色或背景色
  3. 优化1:使用 visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流;
  4. 优化2:避免强制同步布局,在修改 DOM 结构时再去查询该DOM一些相关值。浏览器遇到修改元素样式的代码会存放在渲染队列中;当遇到获取元素样式操作,才会刷新渲染队列;
  5. 优化3:避免布局抖动,在一个 for 循环语句里面不断读取属性值,每次读取属性值之前都要进行计算样式和布局
  6. 优化4:修改元素的样式尽可能使用transform 变形属性或opacity,浏览器内部对其做了硬件加速,这些改变不会引发DOM回流;
  7. 优化5:新增DOM元素采取“批量统一增加”的方案:文档碎片document.createDocumentFragment()

链接

浏览器工作原理与实践