- 前文分析页面加载和DOM生成,讨论JS和CSS是怎么影响DOM生成的,结合了渲染流水线和分层合成机制,现在开始从优化页面速度结合
- 加载阶段,从发出请求到渲染出完整页面过程,主要因素: 网络 、JS脚本
- 交互阶段,页面加载完成到用户交互整合过程,JS影响
- 关闭阶段,用户发出关闭指令页面做出的一些清理操作
- 渲染流水线
- 网络进程请求到html数据之后发送给HTML解析器,然后开始解析HTML,生成DOM,分析出有JS和CSS需要下载,另起一个预解析线程,用于几乎同时下载JS和CSS文件,然后先加载CSS文件,CSS加载,然后开始JS的执行,JS执行完后,再次构建DOM,css解析器解析生成CSSOM,构建,然后构建布局树,布局树通过渲染流水线下一步的绘制,合成等操作渲染出页面
- 关键资源:
- 能阻塞页面首次渲染的资源,即为第一次请求到的HTML,CSS,JS文件,关键资源的大小和个数都会影响到首次页面渲染的速度
- 请求关键资源需要多少个RTT往返时延(Round Trip Time)拆分数据包进行传输,RTT表示从发送端发送数据开始到发送端收到来自接收端的确认总共经历的时延,一个HTTP数据包在14KB左右
- 优化原则: 减少关键资源个数,降低关键资源大小, 降低关键资源的RTT次数,如将JS和CSS写成内联形式,js defer/async 标签添加,压缩CSS,JS,通过减少关键资源的个数和大小减少RTT次数,CDN方式也同样可以
-
渲染进程渲染帧的速度,优化帧率
-
渲染流水线
- 交互阶段没有加载关键资源和DOM,CSSOM构建流程,通常是JS触发交互动画,主线程通过执行JS计算修改DOM和CSSOM,然后经过样式计算,布局绘制,将图层交给合成线程进行栅格化和合成图层,生成页面
- 如果有布局信息的修改,触发重排操作,触发后继续渲染流水线的操作,代价大
- 如果计算样式阶段没有布局信息的修改,则不会触发布局相关信息的调整,跳过布局阶段直接绘制,重绘,代价也不小
- 如果只是对CSS动画渐变等操作只是有CSS触发,并且是是在合成线程上执行的,进入合成阶段,由于是在合成线程中执行的操作,操作非常快,也不阻塞主线程的执行,所以执行合成是效率最高的方式
-
大原则: 单个帧生成速度变快
-
影响帧生成速度和以及优化方法
-
减少JS执行时间,将一次执行的函数分解多个任务, 每次执行时间不要过久,或者采用web workers,这是主线程意外的一个线程,可以执行JS,不过不能操作DOM和CSSOM,所以可以把一些耗时的计算任务放到web worker里面执行
-
避免强制同步布局
-
正常情况下的布局是通过DOM接口执行添加元素或者删除元素后,需要重新 计算样式、布局, 这些操作都是在另外的任务异步完成的,避免当前任务占用主线程时间过多
-
强制同步布局,是JS强制将计算样式和布局操作提前到当前任务
function foo() { let main_div = document.getElementById("main_div") let new_node = document.createElement('li') let textnode = document.createTextNode("time.geekbang") new_node.applyChildren(textnode) document.getElementById("main_div").appendChild(new_node); // 由于下面要获取offestHeight,所以要立即执行布局操作 console.log(main_div.offsetHeight) // 需要获取到新的高度信息,所以要提前进行布局,还得强制让渲染引擎默认执行一次布局操作 }
-
-
避免布局抖动
- 布局抖动,在一次JS脚本执行过程中,多次强制布局和抖动操作
- 避免方式一样是跟避免强制布局一样,尽量不要再修改DOM结构时再去查询一些需要渲染布局的相关值
-
合理使用CSS合成动画
- 在合成线程上执行操作很快,如果提前知道某个元素需要执行动画操作的时候,在元素上标记will-change,告诉渲染引擎要为这个元素单独生成一个图层
-
避免频繁垃圾回收
- 频繁创建临时对象的话垃圾回收也要频频去执行,垃圾回收时会占用主线程,影响到别的任务,所以要尽量避免临时产生的数据,优化存储结构,避免小颗粒对象产生。
-
-
- 系统优化加载阶段和交互阶段
- 加载阶段核心优化原则: 优化关键资源加载速度,优化关键资源个数和大小,减少RTT的花销
- 交互阶段核心优化原则:尽量减少一帧的生成时间,减少单次JS执行时间,使用合成线程进行合成动画处理, 避免强制同步和布局抖动,减少频繁的垃圾回收机制