页面的三个阶段
- 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本
- 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本
- 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作
加载阶段
在页面首次渲染中,并不是所有资源都会产生影响,比如非关键资源,音频,视频,图片等,而关键资源html、css、js文件是会产生影响的,因此基于这个事实可以得出
影响页面首次渲染的核心因素
- 关键资源个数
- 关键资源大小
- 请求关键资源消耗的RTT个数:RTT往返时延指的是从发送端发送数据后,发送端收到来自接收端的确认所经历的时延。通常情况下,1个http数据包在14kb,那么一个0.1MB的页面大概需要8个RTT。在上图中,预解析后下载Js、css文件的时间是重叠的,所以算最长的即可。因此,上图消耗2个RTT。
优化首次渲染速度
- js、css改成内联的形式,若js没有操作DOM或者CSSDOM的话,可加上defer/async,谋体查询,当 JavaScript 标签加上了 async 或者 defer、CSSlink 属性之前加上了取消阻止显现的标志后,它们就变成了非关键资源了
- 压缩文件,删除注释
- 利用CDN加速
交互阶段
因为在交互阶段,帧的渲染速度决定了交互的流畅度。因此讨论页面优化实际上就是讨论渲染引擎是如何渲染帧的,否则就无法优化帧率。和加载阶段的渲染流水线有一些不同的地方是,在交互阶段没有了加载关键资源和构建 DOM、CSSOM 流程,通常是由 JavaScript 触发交互动画的
影响帧生成速度因素及优化方案
减少js脚本执行时间
有时脚本执行时间过长,会严重霸占主线程执行其他渲染任务的时间
- 将任务拆成多个任务
- 采用web workers, 其可以当作主线程之外的一个线程,在里面可执行脚本,但是在web workers中没有DOM、CSSOM环境,在其中访问不到DOM、CSSOM,因此,只适合放一些和DOM、CSSOM无关的操作。
避免强制同步布局
通常情况下,通过DOM接口增加或删除元素之后,重新计算样式布局是在另外一个任务中异步执行的。
function foo() {
let main_div = document.getElementById("mian_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("time.geekbang")
new_node.appendChild(textnode);
document.getElementById("mian_div").appendChild(new_node);
//由于要获取到offsetHeight,
//但是此时的offsetHeight还是老的数据,
//所以需要立即执行布局操作
console.log(main_div.offsetHeight)
}
所谓强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中。
- 为了避免强制同步布局,我们可以调整策略,在修改 DOM 之前查询相关值
function foo() {
let main_div = document.getElementById("mian_div")
//为了避免强制同步布局,在修改DOM之前查询相关值
console.log(main_div.offsetHeight)
let new_node = document.createElement("li")
let textnode = document.createTextNode("time.geekbang")
new_node.appendChild(textnode);
document.getElementById("mian_div").appendChild(new_node);
}
避免布局抖动
function foo() {
let time_li = document.getElementById("time_li")
for (let i = 0; i < 100; i++) {
let main_div = document.getElementById("mian_div")
let new_node = document.createElement("li")
let textnode = document.createTextNode("time.geekbang")
new_node.appendChild(textnode);
new_node.offsetHeight = time_li.offsetHeight;
document.getElementById("mian_div").appendChild(new_node);
}
}
- 尽量不要在修改 DOM 结构时再去查询一些相关值
合理利用CSS合成动画
避免频繁的垃圾回收
我们知道 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。
- 尽可能优化储存结构,尽可能避免小颗粒对象的产生