页面性能

136 阅读3分钟
一个页面主要经历三个阶段:加载阶段、交互阶段、关闭阶段。

一、加载阶段

**
**

分析这张图,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)