我们所谈论的页面优化,其实就是要让页面更快地显示和响应
通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。
- 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。
- 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。
-
加载阶段
图片 音频 视频 等文件 就不会阻塞页面的首次渲染
js 首次请求的 html css 文件是会阻塞首次渲染的,因为在构建 DOM 的过程中需要 HTML 和 JavaScript 文件,在构造渲染树的过程中需要用到 CSS 文件。
这些能阻塞网页首次渲染的资源称为关键资源 第一个是关键资源个数。关键资源个数越多,首次页面的加载时间就会越长。
- 第二个是关键资源大小。通常情况下,所有关键资源的内容越小,其整个资源的下载时间也就越短,那么阻塞渲染的时间也就越短
- 第三个是请求关键资源需要多少个 RTT(Round Trip Time)
RTT 就是这里的往返时延。它是网络中一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。
通常 1 个 HTTP 的数据包在 14KB 左右,所以 1 个 0.1M 的页面就需要拆分成 8 个包来传输了,也就是说需要 8 个 RTT。
由于渲染引擎有一个预解析的线程,在接收到 HTML 数据之后,预解析线程会快速扫描 HTML 数据中的关键资源,一旦扫描到了,会立马发起请求,你可以认为 JavaScript 和 CSS 是同时发起请求的,所以它们的请求是重叠的,那么计算它们的 RTT 时,只需要计算体积最大的那个数据就可以了。
系统性地考虑优化方案了。
总的优化原则就是减少关键资源个数,降低关键资源大小,降低关键资源的 RTT 次数。
- 如何减少关键资源的个数? js css 内联 js 没有代码操作 改成 async defer。css 媒体查询
- 如何减少关键资源的大小 压缩 css js 移除 html css js 注释内容(async defer css 媒体查询)
如何减少关键资源的RTT次数? 可以通过减少关键资源的个数和减少关键资源的大小搭配来实现。除此之外,还可以使用 CDN 来减少每次 RTT 时长。
交互阶段
在交互阶段,帧的渲染速度决定了交互的流畅度。
大部分情况下,生成一个新的帧都是由 JavaScript 通过修改 DOM 或者 CSSOM 来触发的。
还有另外一部分帧是由 CSS 来触发的。
重排 重绘
通过 CSS 实现一些变形、渐变、动画等特效,这是由 CSS 触发的,并且是在合成线程上执行的,这个过程称为合成。因为它不会触发重排或者重绘,而且合成操作本身的速度就非常快,所以执行合成是效率最高的方式
优化方案的原则:
一个大的原则就是让单个帧的生成的速度变快。减少 JavaScript 脚本执行时间 函数分解多个任务 Web Workers
- 避免强制同步布局
```- 布局操作 通过 DOM 接口执行添加元素或者删除元素等操作后,是需要重新计算样式和布局的,不过正常情况下这些操作都是在另外的任务中异步完成的,这样做是为了避免当前的任务占用太长的主线程时间
<p id="demo">强制布局demo</p>
<button onclick="foo()">添加新元素</button>
<script>
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);
}
</script>
- 所谓强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中。
- 为了避免强制同步布局,我们可以调整策略,在修改 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) }
将新的元素添加到 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 循环语句里面不断读取属性值,每次读取属性值之前都要进行计算样式和布局
这种情况的避免方式和强制同步布局一样,都是尽量不要在修改 DOM 结构时再去查询一些相关值
- 合理利用CSS合成动画 wil-change
避免频繁的垃圾回收 JavaScript 使用了自动垃圾回收机制,如果在一些函数中频繁创建临时对象,那么垃圾回收器也会频繁地去执行垃圾回收策略。这样当垃圾回收操作发生时,就会占用主线程,从而影响到其他任务的执行,严重的话还会让用户产生掉帧、不流畅的感觉。所以要尽量避免产生那些临时垃圾数据。那该怎么做呢?可以尽可能优化储存结构,尽可能避免小颗粒对象的产生。
总结
在加载阶段,核心的优化原则是:优化关键资源的加载速度,减少关键资源的个数,降低关键资源的 RTT 次数。
- 在交互阶段,核心的优化原则是:尽量减少一帧的生成时间。可以通过减少单次 JavaScript 的执行时间、避免强制同步布局、避免布局抖动、尽量采用 CSS 的合成动画、避免频繁的垃圾回收等方式来减少一帧生成的时长。