一个页面主要经历三个阶段:加载阶段、交互阶段、关闭阶段。
一、加载阶段
**
**
分析这张图,chrome在接收html字节流时,会在网络进程中开出一个预解析线程,去扫描html文件中的js、css文件,进行提前的下载。
html、css、js文件的下载都影响页面的加载速度,我们把这些阻塞页面首次渲染的资源叫做关键资源。而像图片、音频、视频这些不会阻塞页面首次渲染的资源叫做非关键资源。
1、RTT(Round Trip Time)
RTT指的是一个数据包从发送到收到确认,这样一个来回所花费的时间。
通常一个数据包在14KB,一个140KB的文件,传输完需要10个RTT。
对于RTT不是简单的累加。
举个例子,1个html文件10KB,1个css文件8KB,js文件15KB,
- html需要1RTT就可以了。
- css文件也需要1RTT。
- js文件需要2RTT。
但是chrome开启了预解析线程,js、css是同时发起请求的,时间重叠,所以按照最大的文件的RTT个数算,js+css总共是2RTT。那么整个页面需要3RTT就行了。
所以,对于关键资源的优化有:
-
文件体积尽量小。
-
尽量减少关键资源的个数。一个域名上最多只能同时连接6个请求,超过则会进行排队。可以对js文件设置async、defer变成非关键资源,延后加载。(不操作DOM是可以,此时js不会阻塞html文件的解析),css使用媒体查询,在必要时加载。
-
使用CDN加速,减少每次RTT的时间。
二、交互阶段
交互阶段的优化有:
- 减少js的执行时间。进行任务拆分,试每个任务执行的时间不要太长。
- 对不操作DOM的操作,可以放在Web Workers里面进行,相当于开启一个主线程之外的线程。
1、强制同步布局
<html>
<body>
<div id="mian_div">
<li id="time_li">time</li>
<li>geekbang</li>
</div>
<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>
</body>
</html>
如上代码所示,在performace我们可以看到,首次渲染css样式计算、布局和html解析是同步进行的,而绘制是在之后进行,foo函数执行时,是先执行foo函数,之后才会进行样式的计算和布局。
我们更改foo函数:
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)
}
在插入节点之后,立即获取offsetHeight属性值,但是此时还在js的执行阶段,样式的重新计算和布局还没有开始,现在要获取offsetHeight属性,就导致强行将样式的计算和布局放到了js执行阶段。
2、布局抖动
布局抖动是比强制同步布局更恶劣的存在。等于强制同步布局+抖动。
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循环中不停的更新offsetHeight的值,造成在执行每次执行for循环时,都要去样式计算,布局操作。
注意:在for循环中,获取time_li和new_node的值不会造成强制同步布局。
3、使用cs动画
css动画是合成线程上执行的,因此不占用主线程。
4、避免频繁创建临时变量
频繁的创建临时变量,会造成垃圾回收机制频繁的回收临时变量占用的内存。
补充:
1. 为什么浏览器要使用CSS解析器将样式表转成CSSOM树?
- 渲染引擎并不能识别css样式,所以要转换成能识别的。
- 使js能操作css。
2. 浏览器中发起请求的不一定是浏览器主进程,也可能是渲染进程(引入外部的js,css)