Web前端性能优化实践

Catalogue
  1. 1. 1. 引言
  2. 2. 2. 前端性能优化前的情况分析
    1. 2.1. 2.1 Chrome Network瀑布图分析
    2. 2.2. 2.2 YSlow评级
  3. 3. 3. 采取的前端性能优化技术
    1. 3.1. 3.1 服务器开启Gzip压缩
    2. 3.2. 3.2 使用GruntJS压缩合并组件
    3. 3.3. 3.3 图片压缩与拼接
      1. 3.3.1. 3.3.1 图片格式的选取
      2. 3.3.2. 3.3.2 图片压缩方法
      3. 3.3.3. 3.3.3 图片拼接:CSS Sprites
    4. 3.4. 3.4 添加Expires头
    5. 3.5. 3.5 缩短DNS查找时间
    6. 3.6. 3.6 其他技术
  4. 4. 4. 前端性能优化结果评估
    1. 4.1. 4.1 页面体积与加载时间
      1. 4.1.1. 4.1.1 页面体积
      2. 4.1.2. 4.1.2 页面加载时间
  5. 5. 5. 总结与展望
    1. 5.1. 5.1 详解Use cookie-free domains
  6. 6. 参考文献

摘要: 本文记录了笔者所参与的一个网站平台开发中所采取的Web前端性能优化技术,对优化前后的性能进行分析对比,表明了Web前端性能优化的有效性和必要性。由于水平有限,针对该网站所作的测试肯定存在不科学、不合理的地方,不当之处请谅解。本文仅供相关技术参考之用。

关键字: Web前端;性能优化

1. 引言

Steve Souders(2008)[1] 提出的14条针对网站前端的性能优化技术,让开发者意识到网站的性能并不单单是在服务器端,前端的性能优化也是极其重要的。

“如果我们可以将后端响应时间缩短一半,整体响应时间只能减少5%10%;而如果关注前端性能,同样是将其响应时间减少一半,则整体响应时间可以减少40%45%。”

2. 前端性能优化前的情况分析

2.1 Chrome Network瀑布图分析

开始性能优化之前,网站首次加载main.jsp时,共计加载了25个脚本文件、10个样式表文件以及46张图片,页面的总体积达到3.3MB(如图1所示)。公网10Mb带宽、无缓存情况下,页面完全加载所需时间为30s左右。同样的网络环境下,百度地图、淘宝网等典型网站的加载时间则分别为2s、7.75s(网站与百度地图的网站类型更为相近,之后的对比将主要针对百度地图)。

图1. 各网站静态资源加载对比(重点比较脚本、样式表)

图 1是针对 百度地图天猫商城 的测试结果(2014年12月19日)。由该图可以看出,网站存在两个突出的问题:

  • 文件体积太大;
  • 脚本、样式表请求数太多。

2.2 YSlow评级

用Chrome浏览器的网络模块进行人工观察与分析,难免会有效率低下、分析不全面的问题。因此应当使用自动化的工具进行更深入的分析。

YSlow是雅虎公司开发的一个对网站前端性能进行分析的插件,最初作为Firefox浏览器的插件发布,后来也陆续有了Chrome等浏览器的插件。这里使用Firefox下的YSlow插件对指挥旅游首页性能进行分析,评级结果:优化之前的网站的评级,得分为71,评级C。而百度地图、天猫商城的得分、评级则分别是87 B、77 C。

在23个评级规则中,有4条未能达到A或B级(A意味着性能最优,F意味着有必要进行深入优化),它们应作为网站前端性能优化的主要入手点,分别是:
① F:减少HTTP请求次数
② C:使用内容分发网络(CDN)
③ F:添加较长的Expires头
④ F:使用gzip压缩组件
⑤ E:压缩JavaScript和CSS
⑥ F:Use cookie-free domains,使用无cookie的域名

3. 采取的前端性能优化技术

3.1 服务器开启Gzip压缩

Gzip最早由Jean-loup Gailly和Mark Adler创建,用于UNIX系统的文件压缩。后来成为Internet上数据压缩的常见格式。绝大多数现代浏览器都支持Gzip格式(体现在浏览器发送的HTTP请求头部的字段Accept-Encoding中)。一般来说,开启Gzip压缩之后,至少可以将数据传输体积减少50%,是加速网站的首要工作。

项目所采用的web服务器为tomcat,其开启Gzip压缩服务的方法如下。

{tomcat_root}/confrver.xml文件中的<Connector />中添加如下代码:

1
2
3
4
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla,traviata"
compressableMimeType="textml,text/xml,text/javascript,text/css,text/plain, application/json"

开启成功之后,网站的HTTP响应头部中会使用Content-Encoding来确认响应已经被压缩。

Gzip压缩对网站性能的提升效果是很明显的,例如jquery-1.8.3.min.js的原始体积为91.4KB,Gzip压缩后的体积为33.4KB,压缩率(压缩后体积与压缩前体积之比)为36.5%;登录页面加载的全部文本资源大小为238KB,通过Gzip压缩,实际的传输体积仅为55KB,压缩率达到了23%。

3.2 使用GruntJS压缩合并组件

合并与压缩组件主要针对的是网站的JavaScript脚本与CSS样式表。通过合并文件,减少HTTP请求次数,然后对文件进行压缩以减少文件体积。前端开发中,可以使用GruntJS工具,使合并与压缩的工作更加自动化。

Grunt3是基于NodeJS的一个自动化压缩、合并、测试等构建工具,在安装Node环境后,通过npm进行安装。其相应的任务文件(Gruntfile.js)与依赖包应当放在项目的WebRoot目录下,并且添加到版本控制中。要执行压缩与合并任务,需要获取uglify、cssmin等组件。下面简介Grunt的使用方法。

(1). 安装NodeJS
(2). 安装 Grunt的CLI
全局安装,可能需要管理员权限:npm install -g grunt-cli
(3). 在WebRoot目录下新建package.json,然后执行npm install命令,package.json文件内容如下:

1
2
3
4
5
6
7
8
9
{
"name": "ZHLY",
"version": "0.1.0",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-cssmin": "~0.5.0"
}
}

(4). 新建Gruntfile.js,在里面配置要执行的任务(合并、压缩等)。如下配置Gruntfile.js文件(代码只为示意,略去详细的文件名):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
uglify: {
options: {
banner: '/*压缩文件声明文本...*/\n'
},
build:{
files:[{
src: ['a.js', 'b.js', 'c.js'],
dest: 'abc.min.js'
},{
src: ['d.js'],
dest: 'd.min.js'
}]
}
},
cssmin: {
options: {
keepSpecialComments: 0
},
compress: {
files: [{
src: ["a.css", "b.css"],
dest: 'ab.min.css'
},{
src: ['c.css'],
dest: 'c.min.css'
}]
]
}
}
});

/* 加载任务插件 */
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');

/* 默认被执行的任务列表 */
grunt.registerTask('default', ['uglify', 'cssmin']);
};

(5)在Gruntfile.js的同一目录下,运行grunt命令,就能得到想要的压缩文件。记得将项目中相应的资源引用更改为压缩后的文件名即可。
要注意的是,合并后的压缩文件的加载顺序应当与未压缩时的加载顺序一致,以避免出现例类似undefined的问题。

3.3 图片压缩与拼接

3.3.1 图片格式的选取

网站登录页面的背景图按照1920×1080像素的尺寸进行设计。最初使用的格式为PNG,体积为2.1MB,有线和无线端的下载都很慢。更改为PNG-8后,体积减到1MB,但加载速度仍然很慢。最后更改为JPEG格式,输出质量为80%,图片的体积减小到260KB左右,图片的下载速度明显改善。

PNG、PNG-8、PNG-24以及JPEG格式图片的主要区别如下:

PNG格式适合对透明度有要求的情形;PNG-8处理不了复杂色域下的渐变,PNG-24可以几乎不失真地保留渐变。但是对于色域很广、对透明度没有要求的图片(准确地说是照片),应该毫不犹豫地采用JPEG格式。

因此,大背景图应当首选JPEG格式,而页面中的应用图标则应当使用PNG格式(或者PNG-24)。GIF格式适用于呈现动图,但因其色域窄、质量差,所以其他静态图都尽量使用PNG而非GIF。

3.3.2 图片压缩方法

使用Photoshop自带图片压缩的选项,菜单为:文件–存储为Web所用格式。一般而言,JPEG选择输出质量为70~80%可以保证视觉上几乎没有差异,当然要因图片而异,有时输出质量为原图的30%也可以保证较高的视觉质量。
还有一个专门对JPEG格式的图片进行有损压缩的工具:JPEGmini [4]

3.3.3 图片拼接:CSS Sprites

CSS Sprites(CSS图片拼接,或CSS精灵图)利用的是CSS backgrond-position属性的特点:可以自定义背景图片相对于容器视口的坐标位置。使用这种技术,可以将许多小图片放到同一张图片里面,从而减少了HTTP请求,提高网页加载速度。
代码示意: background-position: x y;

3.4 添加Expires头

现代浏览器通常会默认使用缓存来减少HTTP请求的数量,并减小HTTP响应的大小,使Web页面加载得更快。Web服务器使用Expires头来告诉浏览器它可以使用一个组件的当前副本,直到指定的时间为止。这样在第二次试图请求某个资源时,便可能从浏览器的缓存中读取,避免了网络传输,从而加快页面加载速度。

根据Apache Tomcat文档的说明,在 {tomcat_root}/conf/web.xml 文件中的相应位置添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<filter>
<filter-name>ExpiresFilter</filter-name>
<filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
<init-param>
<param-name>ExpiresByType image</param-name>
<param-value>access plus 1 month</param-value>
</init-param>
<init-param>
<param-name>ExpiresByType text/css</param-name>
<param-value>access plus 1 week</param-value>
</init-param>
</filter>
...
<filter-mapping>
<filter-name>ExpiresFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>

可以为图像(image)、样式表(text/css)、脚本(application/javascript)等静态文件的HTTP请求响应头添加 ExpiresCache-Control: max-age= 头信息。

过期时间的选择应当依据项目开发进度、网站更新频率、用户使用频率等来决定。例如,图片通常是很少进行修改的,而脚本、样式表的更新则较为频繁,考虑到是在开发阶段,因此图片的过期时间设为1个月,而脚本、样式表的过期时间设为1周。

3.5 缩短DNS查找时间

图3. DNS解析过程示意图

现代浏览器通常具有DNS预解析(DNS Prefetch)功能,即加载网页后对页面中的超链接进行DNS解析并将结果缓存在浏览器中,便于用户点击时减少DNS解析时间(目前的DNS解析时间通常为200ms)。但是对于应用型的Web页面(或称单页面应用)而言,对页面内的超链接进行DNS预解析并无太大意义,因为几乎没有超链接。重要的是第一次执行DNS解析时的性能。

域名管理中的TTL(Time to Live)字段用于高速远程域名服务器可以缓存本记录多长时间。根据Andrew等人(2012)的建议,如果服务器的IP非常稳定(例如多年不变),那么将TTL值设为1天可能是安全的;而对于波动比较大的信息,几秒钟或一分钟后清除掉记录的做法或许更安全。

在购买到网站域名后,最初是将其TTL设为10分钟。这样在经过较短的时间(几个小时)后,该域名就已经被大部分的DNS服务器记录下来了。现在考虑到本系统的IP为固定IP,主机也在自己的机房内,而且没有备份,因此将TTL设大一点是可以的。目前将TTL值设为了1小时,可以使某个用户在1个小时内再次访问或请求资源时不必再进行完整的DNS解析,而是使用缓存结果。

3.6 其他技术

其他优化技术包括目前已成为业界实际标准的做法,例如:

① 将脚本引用置于页面底部;
② 将样式表引用置于页面顶部;
③ 不在页面中缩放图片;
④ 避免CSS表达式;
⑤ Ajax请求尽可能使用GET方式;
等等。

这些在此不做详细分析。

4. 前端性能优化结果评估

4.1 页面体积与加载时间

4.1.1 页面体积

浏览器视口分辨率:1920×746像素。图片的优化只是针对HTTP请求次数,其前后尺寸变化不大,因此暂不考虑优化前后的差别。JavaScript请求数量“*+4”表示其中4个请求来自其他域(主要是搜狗地图服务)。

优化前后文件体积压缩率 = 优化后的体积÷优化前的体积

优化前Grunt之后Gzip之后优化前后文件体积压缩率
请求数体积请求数体积请求数体积
JS21+42600 KB8+51037 KB8+5375 KB14.4%
CSS11464 KB10382 KB1080.4 KB17.3%
Total1103300 KB751903 KB75791 KB24.0%

在这里,压缩后的JS文件平均大小为80KB,CSS文件的平均大小为38.2KB。

文件压缩之后究竟应该多大才算合适?目前也无定论,只能根据经验来判断。合并后的文件如果太大,则对单个文件的下载、解析、执行的耗时就越长(当然,随着浏览器能力的不断提升,这个时间相比于网络传输时间仍是小量)。《Web性能权威指南》里提到:

谷歌PageSpeed团队的测试表明,30~50KB(压缩后)是每个JavaScript文件大小的合适范围:既大道了能够减少小文件带来的网络延迟,还能确保递增及分层式的执行。
page 176.

4.1.2 页面加载时间

10Mb带宽下,操作系统Windows 8 x64,内存8GB,CPU Intel i5,屏幕分辨率1366×768像素,使用Chrome浏览器(主版本号38)于2014-12-25 21:08至21:45,每隔1~2分钟对网站主页面/main.jsp进行无缓存加载时间测试(每次刷新页面之前,都先执行清空浏览器缓存的操作);同时,也对第二次加载的时间进行了记录,以作为有无缓存的性能对比。详细结果如图 4所示。由图可见,经过优化后,页面无缓存的情况下完全加载时间为3.31秒,比优化之前减少了一个数量级,加载速度有很明显的提升。

图4. 网站优化前后页面加载时间对比

###4.2 YSlow评级

在实施了上面的优化措施之后,用YSlow工具对网站主页面进行评级,得分80,等级为C,得分为F的项目由6降为3个,与优化之前相比有所提高。
YSlow给出的结果表明网站仍存在可以继续优化的方面,具体见下节。

5. 总结与展望

网站前端性能优化中最首要的原则是:优先针对瓶颈进行优化。所谓瓶颈,乃是制约性能的最关键因素。对于网站,其第一瓶颈为Gzip压缩未开启。开启压缩之后的瓶颈,则是组件未压缩。对于中小型网站来说,解决了前几个瓶颈问题,性能问题也就解决了一大半。

YSlow评级仍为3个方面给出F评分:
① F:减少HTTP请求次数;
② F:添加较长的Expires头;
③ F:Use cookie-free domains,使用无cookie的域名。

其中,①是仍需继续优化的方面;至于②,由于使用的CDN和搜狗地图服务对其资源并未添加Expires头,导致本项评分较低,但本域下的资源的Expires头都已经符号符合其评判标准了;而对于②,由于目前服务器只申请了一个域名,静态资源大都从该域名下获取,导致评分较低。这些都是未来可以进一步开展优化的方面。

HTTP 1.0增加了请求和响应头部,以便双方能够交换有关请求和响应的元信息。最终,HTTP 1.1把这种格式变成了标准:服务器和客户端都可以轻松扩展首部,而且始终以纯文本形式发送,以保证与之前的HTTP版本的兼容。
今天,每个浏览器发起的HTTP请求,都会携带额外500800字节的HTTP元数据:用户代理字符串、很少改变的接收和传输首部、缓存指令等等。有时候,500800字节都说少了,因为没有包括最大的一块:HTTP cookie。现代应用经常通过cookie进行会话管理、记录个性选项或者完成分析。综合到一起,所有这些未经压缩的HTTP元数据经常会给每个HTTP请求增加几千字节的协议开销。
——《Web性能权威指南》

对于本项目这种需要用户名、密码验证登陆的网站,观察其主域下所有资源的请求头(包括document、css、javascript、images等),会发现全部的HTTP请求头部(Request Headers)中都把该域下的Cookie给发送过去了,即使服务器不需要。而且这个cookie还是未经任何压缩的。

浏览器执行“每个服务端最大连接数”的限制是根据URL上的主机名,而不是解析出来的IP地址。
……
这对那些想把内容分配到多个域的人来说是个好消息,他们不必额外部署服务器,而是为新域建立一条CNAME记录。CNAME仅仅是域名的一个别名。即使域名都指向同一个服务器,浏览器依旧会为每个主机名开放最大连接数。
——《高性能网站建设进阶指南》

目前很多网站的常见做法是分离图片,即让网站的文本资源从域1下载的同时,让所需的全部图片资源从域2开始并行下载。

此技术的具体实施方法:基本思路是在域名解析中添加CNAME记录,然后在工程中将资源分离;具体如何写代码,【待研究】。

参考文献

[1]. Steve Souders. High Performance Web Sites [M].
[2]. YSlow官网,http://yslow.org/
[3]. Grunt中文网,http://www.gruntjs.net/
[4]. JPEGmini官网,http://www.jpegmini.com/
[5]. Andrew S. Tanenbaum, David J. Wetherall著, 严伟, 潘爱民 译. 计算机网络(第五版)[M]. 北京:清华大学出版社, 2012.
[6]. Ilya Grigorik. High Performance Browser Networking [M]. 李松峰 译. Web性能权威指南 [M]. 北京:人民邮电出版社, 2014.
[7]. Steve Souders. Even faster Web Sites: Performance Best Practice for Web Developers [M]. 口碑网前端团队 译. 高性能网站建设进阶指南, 北京: 电子工业出版社, 2010.

Share