如何分析页面加载慢

一般情况下,如果网页加载时间超过5s,用户就会感觉页面比较卡,遇到耐心不好的用户,肯定就直接关闭走人了,所以加载的时间对于一个网站来说还是相当重要的。如果是你的头头,给你一个url,让你分析页面为什么这么卡,你该如何办呢?

查看页面加载时间

现在的浏览器都是有开发者调试模式的,以chrome为例,打开页面后,按ctrl+shift+i,或者点击右键菜单里的检查,就进入了开发者模式,开发者模式里面有很多功能,跟页面加载时间相关的就是network标签。如下图所示:

chrome开发者模式

在最下面的数据反应了该页面的整体加载情况。比如总共有111个请求,5.8M的数据,以及三个时间点,finish、DOMContentLoaded和load。
finish:页面最后一个请求截止的时间,如果页面加载完后,触发了ajax请求,那么该时间会变更。

DOMContentLoaded:dom内容加载并解析完成的时间
load:页面所有的资源(图片,音频,视频等)加载完成的时间

那什么是dom内容加载完毕呢?我们从打开一个网页说起。当输入一个URL,页面的展示首先是空白的,然后过一会,页面会展示出内容,但是页面的有些资源比如说图片资源还无法看到,此时页面是可以正常的交互,过一段时间后,图片才完成显示在页面。从页面空白到展示出页面内容,会触发DOMContentLoaded事件。而这段时间就是HTML文档被加载和解析完成。

在这里我们可以明确DOMContentLoaded所计算的时间,当文档中没有脚本时,浏览器解析完文档便能触发 DOMContentLoaded 事件;如果文档中包含脚本,则脚本会阻塞文档的解析,而脚本需要等位于脚本前面的css加载完才能执行。在任何情况下,DOMContentLoaded 的触发不需要等待图片等其他资源加载完成。

接下来,我们来说说load,页面上所有的资源(图片,音频,视频等)被加载以后才会触发load事件,简单来说,页面的load事件会在DOMContentLoaded被触发之后才触发。

通俗来讲就是DOMContentLoaded是页面白屏的时间,load是通常所说的页面加载完成,浏览器不再转菊花的时间。

影响页面加载的因素

js性能太差,阻塞页面

因为浏览器解析过程中,遇到<script>标签的时候,便会停止解析过程,转而去处理脚本,如果脚本是内联的,浏览器会先去执行这段内联的脚本,如果是外链的,那么先会去加载脚本,然后执行。在处理完脚本之后,浏览器便继续解析HTML文档。
遇到js阻塞的情况,就需要具体去看代码的具体逻辑了,在这里不做详述。

某个请求慢阻塞页面的加载

一般遇到页面卡顿,我们首先都是会想到去network里面去查看,可以一眼便能看出页面具体的加载时间,以及每一个请求的详细耗时。比如我们打开爱奇艺《2017你的追剧报告》这一活动页。

image.png

这一页面其实在用户看来应该是5s左右就渲染完成,但是浏览器却一直转菊花,转了足足1分钟。最后显示load的时间为1.2min,是为什么呢?

image.png

我们观察network里的请求,status为200的毫无疑问是正常的,然后我们发现其中有一个一直pending状态的,这个状态的请求其实就是我们要找的罪魁祸首,浏览器会一直等待这请求返回,直至超时或failed,才会触发load事件。

同域名下的请求数过多导致Queueing

HTML页面是如何完成

为了方便理解,我们首先需要知道一个页面请求是如何完成的,即当我们在浏览器输入url按下enter后,究竟发生了一些什么事,这个应该是个经典面试题了,搜搜资料一大把,这里不详细解释。还是以爱奇艺那个活动页为例,url为http://www.iqiyi.com/common/2017summary.html
第一浏览器拿到你输入的url,解析出域名www.iqiyi.com,得到域名之后肯定要与这个服务器建立连接,而TCP/IP协议中连接是需要知道IP地址,所以首先需要走DNS查询,解析得到域名IP,然后再与该IP地址来三次经典的握手建立TCP连接,如果协议头是http则与目标IP的80端口通信,如果是https则是443端口,建立好TCP连接后,就可以发送http请求了,告诉服务器我需要get /common/2017summary.html这个地址下的文件内容,服务器收到请求,然后将这个2017summary.html超文本返回个浏览器,浏览器拿到这个html开始解析,blalalala......,最终就是我们所看到的页面内容。

页面里单个请求的各个时间段的含义

一般情况下html页面在解析渲染的过程中,会有大量的其他请求,比如外联的css、js、图片等等,这些资源都需要浏览器去重新发起http请求。这些请求其实也是类似的一个过程
我们再仔细看看这些单个请求的各个时间段,将鼠标悬停在waterfall字段,就是那个五颜六色的。可以看到这个请求具体的耗时,如下图所示:

image.png

https://developers.google.com/web/tools/chrome-devtools/network-performance/reference#timing-explanation

以下是各个时间段的含义:

Stalled/Blocking
Time the request spent waiting before it could be sent. This time is
inclusive of any time spent in proxy negotiation. Additionally, this
time will include when the browser is waiting for an already
established connection to become available for re-use, obeying
Chrome's maximum six TCP connection per origin rule.
Proxy Negotiation
Time spent negotiating with a proxy server connection.
DNS Lookup
Time spent performing the DNS lookup. Every new domain on a page
requires a full roundtrip to do the DNS lookup.
Initial Connection / Connecting
Time it took to establish a connection, including TCP
handshakes/retries and negotiating a SSL.
SSL
Time spent completing a SSL handshake.
Request Sent / Sending
Time spent issuing the network request. Typically a fraction of a
millisecond.
Waiting (TTFB)
Time spent waiting for the initial response, also known as the Time To
First Byte. This time captures the latency of a round trip to the
server in addition to the time spent waiting for the server to deliver
the response.
Content Download / Downloading
Time spent receiving the response data.

以下是翻译的中文版,翻译水平有限
Queueing请求排队的时间。关于这个,需要知道一个背景,就是浏览器与同一个域名建立的TCP连接数是有限制的,chrome设置的6个,如果说同一时间,发起的同一域名的请求超过了6个,这时候就需要排队了,也就是这个Queueing时间

Stalled是浏览器得到要发出这个请求的指令,到请求可以发出的等待时间,一般是代理协商、以及等待可复用的TCP连接释放的时间,不包括DNS查询、建立TCP连接等时间等
DNS Lookup DNS查询的时间,页面内任何新的域名都需要走一遍 完整的DNS查询过程,已经查询过的则走缓存
Initial Connection / Connecting 建立TCP连接的时间,包括TCP的三次握手和SSL的认证
SSL完成ssl认证的时间
Request sent/sending请求第一个字节发出前到最后一个字节发出后的时间,也就是上传时间
Waiting 请求发出后,到收到响应的第一个字节所花费的时间(Time To First Byte)
Content Download 收到响应的第一个字节,到接受完最后一个字节的时间,就是下载时间

这时候我们再回过头来看爱奇艺活动页http://www.iqiyi.com/common/2017summary.html?uid=1417762088,我们刚刚已经找到了一个阻塞页面的请求,那个请求只是一个艾瑞统计的get请求,对页面的业务逻辑无关紧要,暂时配置host让这个请求直接404,这样不会阻塞页面加载。

配置host后,页面加载的时间一般是3s,偶尔会超过5s情况,页面先是变绿,然后变黑提示downloading,最后展示页面。

这时候要再分析页面的性能瓶颈就比较困难了。因为粗略扫一眼请求的时间,都是清一色几十毫秒。这时候我们就需要分析那些最后完成的几个请求耗时情况了,这可以在network里瀑布般的Timeline里很直观的看出来

image.png

在timeline里,每一种颜色代表请求一个时间段,比如DNS查询是深绿色,content download是蓝色

image.png

我们再仔细观察瀑布流的Timeline,里面每一条线代表就是一个TCP的连接,每条线的颜色一直循环变化,从灰色到深绿最后蓝色,然后又变灰色,说明浏览器建立的TCP连接一直在循环复用,这样就省去了三次握手的时间。瀑布流图中,有一条蓝色竖线和红色竖线,蓝色竖线表示触发domContentLoad事件触发时间,红色表示Load事件触发

最后我们发现,耗时比较长的TCP连接有6个,请求的都是图片,而且是同一个域名,请求的数量达到100个,所以从这里我们也可以看出,chrome对于同一域名的TCP连接最多建立6个,如果请求过多,就会出现排队,也就是Queueing时间,我们挑几个最后请求看看便知。

image.png
image.png

从上图来看,最后几个图片的请求,排队时间已经接近1s了,而request sent、waiting、content download时间是非常少的。然后再仔细看,后面的图片请求都没有Initial Connection / Connecting时间,也就是没有了TCP的建立过程

image.png

最后,我们大概知道这个页面的性能瓶颈了,因为图片过多,然后点击右键查看源代码,看到开发定义了一个长长的数组,然后循环创建图片,再给图片的src赋值,这样就会触发浏览器发送图片请求。

        function loading() {
            $('.m-loading').removeClass('hide');
            var resCount = 0;
            var length = images.length;
            for (var i = 0; i < length; i++) {
                var image = new Image();
                image.src = images[i];
                image.onload = onImageLoad;
                image.onerror = onImageError;
            }

那个黑色loading提示浮层,其实背后就是在预加载这些图片,这样之后在使用这些图片的时候,就会稍微流畅一点,因为省去了请求的时间。但是这100来张图片,其实并不是每次都需要加载,因为页面分主态和客态,不同的视角用到的图片不是一样,所以大概20%的图片其实是可以省去的,因为根本用不上,当然这就需要判断,什么情况下需要加载哪些图片。

利用chrome的performance开发工具

image.png

parse html或layout时间很长

image.png

image.png

image.png

从图中看layout花了500+ms,这很可能是第一个请求比较慢导致。第一个请求总耗时500ms左右,主要消耗在content download,跟网速关系较大,还是html内容太多,size那一列有两行,上面的表示请求实际返回的数据大小(响应头+响应内容),大多数会采用采用gzip压缩(只压缩响应内容,不压缩响应头),所以会比较小,下边的是表示响应内容的大小(解压后的)。那22kb下载了半秒,确实有点慢

参考

http://blog.csdn.net/itpinpai/article/details/52574385
http://blog.csdn.net/playboyanta123/article/details/46815441
https://segmentfault.com/q/1010000002399481/a-1020000002399855
https://www.cnblogs.com/caizhenbo/p/6679478.html
https://www.cnblogs.com/larennani/p/6741289.html
https://www.cnblogs.com/ranzige/p/4097453.html