谈论的⻚⾯优化,其实就是要让⻚⾯更快地显⽰和响应。
通常⼀个⻚⾯有三个阶段:加载阶段、交互阶段和关闭阶段。
- 加载阶段,是指从发出请求到渲染出完整⻚⾯的过程,影响到这个阶段的主要因素有⽹络和JavaScript脚 本。
- 交互阶段,主要是从⻚⾯加载完成到⽤⼾交互的整合过程,影响到这个阶段的主要因素是JavaScript脚 本。
- 关闭阶段,主要是⽤⼾发出关闭指令后⻚⾯所做的⼀些清理操作。
加载阶段

在构建DOM的过程中需要HTML和JavaScript⽂件,在构造渲染树的过程中需要⽤到CSS⽂件。
而Javascript和CSS文件请求造成网页阻塞,我们把这些能阻塞⽹⻚⾸次渲染的资源称为关键资源。
三个影响⻚⾯ ⾸次渲染的核⼼因素。
第⼀个是关键资源个数。关键资源个数越多,⾸次⻚⾯的加载时间就会越⻓。⽐如上图中的关键资源个数就 是3个,1个HTML⽂件、1个JavaScript和1个CSS⽂件。
第⼆个是关键资源⼤⼩。通常情况下,所有关键资源的内容越⼩,其整个资源的下载时间也就越短,那么阻 塞渲染的时间也就越短。
第三个是请求关键资源需要多少个RTT(Round Trip Time),RTT是往返时延。它是网络中的一个重要的性能指标。通常一个HTTP的数据包在14KB左右,所以一个0.1MB大小的页面需要拆分为8个包,也就是需要8个RTT。
总的优化原则就 是减少关键资源个数,降低关键资源⼤⼩,降低关键资源的RTT次数
如何减少关键资源的个数?
⼀种⽅式是可以将JavaScript和CSS改成内联的形式,这样可以减少关键资源的请求个数。
如果JavaScript代 码没有DOM或者CSSOM的操作,则可以改成sync或者defer属性。
对于CSS,如果不是在构建⻚⾯之 前加载的,则可以添加媒体取消阻⽌显现的标志。
如何减少关键资源的⼤⼩?
可以压缩CSS和JavaScript资源,移除HTML、CSS、JavaScript⽂件中⼀些注 释内容
如何减少关键资源RTT的次数?
可以通过减少关键资源的个数和减少关键资源的⼤⼩搭配来实现。除此之 外,还可以使⽤CDN来减少每次RTT时⻓。
交互阶段

如果在计算样式阶段发现有布局信息的修改,那么就会触发重排操作,然后触发后续渲染流⽔线的⼀系列操 作,这个代价是⾮常⼤的。
如果在计算样式阶段没有发现有布局信息的修改,只是修改了颜⾊⼀类的信息,那么就不会涉及到布局 相关的调整,所以可以跳过布局阶段,直接进⼊绘制阶段,这个过程叫重绘。不过重绘阶段的代价也是不⼩ 的。
通过CSS实现⼀些变形、渐变、动画等特效,这是由CSS触发的,并且是在合成线程上 执⾏的,这个过程称为合成。通过CSS实现⼀些变形、渐变、动画等特效,这是由CSS触发的,并且是在合成线程上 执⾏的,这个过程称为合成。
在交互阶段渲染流⽔线中有哪些因素影响了帧的⽣成速度以及 如何去优化
减少JavaScript脚本执⾏时间
有时JavaScript函数的⼀次执⾏时间可能有⼏百毫秒,这就严重霸占了主线程执⾏其他渲染任务的时间。针 对这种情况我们可以采⽤以下两种策略:
- ⼀种是将⼀次执⾏的函数分解为多个任务,使得每次的执⾏时间不要过久。
- 另⼀种是采⽤Web Workers。你可以把Web Workers当作主线程之外的⼀个线程,在Web Workers中是可 以执⾏JavaScript脚本的,不过Web Workers中没有DOM、CSSOM环境,这意味着在Web Workers中是⽆法通过JavaScript来访问DOM的,所以我们可以把⼀些和DOM操作⽆关且耗时的任务放到Web Workers中去执⾏。
总之,在交互阶段,对JavaScript脚本总的原则就是不要⼀次霸占太久主线程。
避免强制同步布局
所谓强制同步布局,是指JavaScript强制将计算样式和布局操作提前到当前的任务中。
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)
}
将新的元素添加到DOM之后,我们⼜调⽤了main_div.offsetHeight来获取新main_div的⾼度信息。 如果要获取到main_div的⾼度,就需要重新布局,所以这⾥在获取到main_div的⾼度之前,JavaScript还 需要强制让渲染引擎默认执⾏⼀次布局操作。我们把这个操作称为强制同步布局。
避免布局抖动
所谓布局抖动,是指在⼀次JavaScript执⾏过程 中,多次执⾏强制布局和抖动操作。
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);
}
}
for循环语句⾥⾯不断读取属性值,每次读取属性值之前都要进⾏计算样式和布局。
在foo函数内部重复执⾏计算样式和布局,这会⼤⼤影响当前函数的执⾏效率
这种情况 的避免⽅式和强制同步布局⼀样,都是尽量不要在修改DOM结构时再去查询⼀些相关值。
合理利⽤CSS合成动画
合成动画是直接在合成线程上执⾏的,这和在主线程上执⾏的布局、绘制等操作不同,如果主线程被 JavaScript或者⼀些布局任务占⽤,CSS动画依然能继续执⾏。
如果能提前知道对某个元素执⾏动画操作,那就最好将其标记为will-change,这是告诉渲染引擎需 要将该元素单独⽣成⼀个图层。
总结
在加载阶段,核⼼的优化原则是:优化关键资源的加载速度,减少关键资源的个数,降低关键资源的RTT次数。
在交互阶段,核⼼的优化原则是:尽量减少⼀帧的⽣成时间。可以通过减少单次JavaScript的执⾏时间、避 免强制同步布局、避免布局抖动、尽量采⽤CSS的合成动画