1.2.1 白屏时间的重要性
简单理解,即用户打开浏览器输入网站的URL后,从屏幕空白到第一个画面出来的时间,这个时间的长短将直接决定网站页面给用户的第一印象。
图1-1是一张页面加载过程的视频截图,可以很形象地反映白屏的过程。

图1-1
在很多商用或者开源的基调性能系统(比如 Webpagetest)中,将白屏时间命名为StartRender,而在最新的Chrome、IE浏览器提供的Performance Timing API中也提供了类似的称为FirstPaint的指标。
StartRender或FirstPaint代表浏览器开始渲染的时间,从用户的角度来看,就像图1-1中所表现出来的,即从用户在浏览器地址栏中输入URL并按Enter键开始,到在页面上看到第一个画面的时间间隔。
重要性:页面渲染的时间越短,用户等待的时间就越短,用户感知到的页面速度就越快,这样可以大大提高用户体验,减少新用户的跳出,提高留存率。反之,过长的等待时间,会让用户变得烦躁,更轻易跳出或者关闭这个网站。
1.2.2 白屏过程详解
白屏过程我们通过图1-2来说明。从中可以看到浏览器与服务器做了哪些交互,又在客户端经历了哪些过程。

图1-2
接下来,我们依次说明每个步骤。
1.DNS Lookup
简单地理解,DNS Lookup即浏览器从DNS服务器中进行域名查询。浏览器解析域名拿到对应的 IP 地址后,才能和服务器进行通信。通常浏览器在加载页面的过程中,会进行很多次DNS Lookup操作,其中包括页面本身的域名查询,以及浏览器在解析页面HTML代码过程中,需要加载JS、CSS、Image等资源产生的解析。
对大中型网站来说,通常在页面加载过程中会产生下列域名解析:
● www.example.com,页面URL本身。
● style.example.com,JS、CSS等静态资源请求。
● img.example.com,静态图片请求。
● ajax.example.com,各种站内的Ajax请求。
● *.google.com,其他比如Google的站外请求。
域名解析的过程,可短至几十毫秒,也可能消耗几秒,所以解析过程的快慢直接影响页面整体加载的渲染速度,特别是对于每天访问量达千万级别的页面。
DNS解析速度的优化策略有很多,通常我们会从以下几个方面思考细节的优化:
● DNS缓存优化。
● DNS预加载策略。
● 页面中资源的域名的合理分配。
● 稳定可靠的DNS服务器等。
后面会有专门的章节来详解DNS Lookup及其优化策略。
2.建立TCP请求连接
浏览器和服务端TCP请求建立的过程,基于TCP/IP,全称为Transmission Control Protocol/Internet Protocol,该协议由网络层的IP和传输层的TCP组成。IP作为每一台联网设备在Internet中的地址,是建立传输通道的基础。
浏览器针对各个域名进行DNS Lookup得到IP地址后,就开始建立TCP请求连接的过程。
TCP通过三次握手建立连接,并提供可靠的数据传输服务。
我们可以通过下面两个要素理解TCP连接及传输的过程。
● 网络传输链路建立。
● 数据传输确认。
基于上面两个要素,我们简单梳理一下TCP优化的思路,后面会有专门的章节来详解TCP连接传输及其优化策略。
在真实的网络环境中,网络节点错综复杂,TCP建立的网络传输链路并不一定如你所愿,不是每条链路都是高速公路,不是每条高速公路都能保持通畅。同时,数据在传输过程中不可避免会出现丢失或者被破坏的情况。
那么我们该如何应对呢?
● 基于不同的网络环境,优化数据包的大小,以减少数据因传输丢失或者被破坏产生的重传,从而提高传输效率。
● 网络传输链路的优化,对于大部分企业来说,是需要很大投入的。例如,尝试在部署在不同国家的后台服务系统之间,有针对性地建立高速的专属网络通道,或者通过购买第三方内容网络服务来加速浏览器和 Web 服务器之间的网络通道优化,可以想象投入是无比巨大的。如何整合各种优质资源,帮助我们达到链路优化的目标,会在 TCP 优化相关章节中讲述。
3.服务端请求处理响应
在TCP连接成功建立后,Web服务器接受请求,开始请求处理,而浏览器端则开始等待服务器的处理响应。
以一个动态的HTML请求举例,通常Web服务器在接收到此类请求后,会做出下面一系列处理。
(1)Web 服务器根据请求类型,将请求转发给已经注册该请求的应用服务器来处理。如果网站服务器的前端架构是通过 Apache+JBoss 来实现的,则 Apache 会将请求转发给后端的JBoss 应用服务容器来处理。相反如果是一个未做过其他应用服务器注册的请求类型,则 Web服务器会自己来处理。比如静态页面、图片等请求,Web 服务器会通过对文件系统进行 I/O,获取内容,然后跳过后面的步骤,直接进行Gzip压缩后输出。
(2)应用服务器接收请求,分配给对应的代码单元处理。
(3)从缓存、数据库、文件系统等获取数据。在大型网站的后台,通常采用分布式的服务架构,各个业务数据的获取是以中间件的形式通过各个相互依赖的服务相互调用的过程。
(4)基于业务进行的数据逻辑处理,可以简称为业务逻辑层。
(5)基于模板进行数据格式化渲染。
(6)应用服务器将格式化渲染的数据返回Web服务器。
(7)Web服务器将最终要响应(Response)到客户端的内容,经过Gzip压缩(通常网站管理员会开启这个配置)后,返回客户端。
通过列举这些过程我们可以看到,服务端的请求处理速度优化,是一个非常复杂的专题,它涵盖的范围非常广,并且对于不同的网络平台,也有不一样的优化策略。
对于中小型网站来说,重心可能主要放在数据获取过程的优化。其中对缓存、数据库等处理的优化,可为网站页面带来较大的响应速度提升。
而对于有着亿级流量的大型网络平台,每一个过程的优化都可能带来质的提升。比如被很多人忽略的模板渲染过程,通过优化模板渲染逻辑,可使模板渲染速度提高一倍;或者通过调整Gzip算法及响应内容的代码结构,来提高Gzip压缩率、减小压缩包大小等。
关于服务端的优化策略,后面会有专门的章节来讲述。
服务端完成请求处理,开始响应客户端内容(在Web页面中,客户端的角色主要由浏览器来扮演),即开始了在前端由浏览器显示页面的过程。
4.客户端下载、解析、渲染显示页面
服务器返回HTTP Response后,浏览器陆续开始接收数据,进行HTML下载、解析、渲染显示等过程。
具体步骤如下。
(1)如果是Gzip包,则先解压为HTML。
(2)解析HTML的头部代码,下载头部代码中引用的样式资源文件或者脚本资源文件。
(3)解析HTML代码和样式文件代码,这个过程会构造出两个树结构,即与HTML相关的DOM树,以及与CSS相关的CSSOM树。
(4)通过遍历DOM树和CSSOM树,浏览器依次计算每个节点的大小、坐标、颜色等样式,构造出渲染树。
(5)根据渲染树完成绘制的过程。
上面的渲染过程,我们可以使用Chrome的控制台开发工具中的TimeLine进行观察,如图1-3所示。

图1-3
图1-3中①代表ParseHTML,②代表Scripting,③代表RecalculateStyle和Layout过程,④代表Paint过程。
基于上面的分析,我们可以知道理想的页面显示过程如图1-4所示。

图1-4
浏览器下载 HTML 后,首先解析头部代码,进行样式表下载,然后继续向下解析 HTML代码,构造DOM树,同时进行样式下载。DOM树构建完成后,立即开始构造CSSOM树。如果样式表的下载速度足够快,DOM树和CSSOM树就进入一个并行的过程,当两棵树准备完毕,即可开始构造渲染树,最后进行绘制。
而在真实页面解析过程中,浏览器通常会因为各种因素被阻断。
(1)HTML代码中的JavaScript代码(简称JS代码)会阻断DOM树的构造,因为浏览器认为这段JS代码可能会修改DOM结构,所以必须等待JS代码执行完毕,再恢复DOM树的构造过程。这是由浏览器的安全解析策略决定的,目前并没有指定某个JS代码不涉及DOM的属性。
(2)浏览器必须等待样式表加载完成,才能开始构建CSSOM树。
(3)还有一种特殊情况,浏览器在解析HTML时遇到JS代码,而此时CSSOM树还未构建完成,则浏览器会暂停脚本的执行(浏览器同时也会暂停继续向下解析HTML代码,从而导致DOM树的构建过程被暂停阻塞),直到CSS样式文件下载完成,并完成CSSOM树的构建,才会重新恢复原来的解析。这也是由浏览器的安全解析策略决定的。
通过对上面各因素的分析,我们可以发现,HTML中的内联JS代码执行的危害之处,不在于它阻断了 DOM 树的构建过程,除非是一段特别恶劣的 JS 代码运行非常慢。通常内联的 JS代码运行大概消耗几十毫秒,也就是暂停构建几毫秒到几十毫秒。它最大的危害在于上面第三种情况提到的,即DOM树构建被阻塞的时间不是只有JS代码运行的时间,而是会加上样式资源文件下载和 CSSOM 树的构建时间,这时浏览器所进行的串行解析过程,与我们在前面期望的DOM树和CSSOM树[1]的并行解析过程相差甚远。我们用图1-5来表示这个过程。

图1-5
简单地对比图1-4和图1-5,每一个步骤的时间段可能是一样的,但意外的阻塞等待导致总体消耗时间大大加长了。
所以,关于浏览器下载、解析、渲染显示页面的优化策略,根据渲染步骤,大概可以从以下几个方面着手:
● 优化HTML代码和结构,缩短HTML下载时间,加快HTML解析速度。
● 优化CSS文件和结构,缩短CSS文件下载时间和解析时间。
● 合理放置JS代码,避免前面第三种情况的出现,这也是最重要的。