前端近几年变化很大,各种工具,库,框架并发。虽然如此,但是网站前端性能优化的思路基本没变。
为什么前端性能如此重要?数据显示:
1)只有 10%~20%的最终用户响应时间花在了下载HTML文档上。其余的80%~90%时间花在了下载页面中的所有组件中;
2)另外一点是,优化后台需要花费比较大的成本,优化前端只需要适当地遵循一些法则会有较大的提升,相对低成本高收益。
最近读了 Steve Souders 的《高性能网站建设指南》,觉得很不错在此做一下总结。Steve是Firebug和Yslow的作者,相信大家都使用过。虽然书中的很多手段都已经过时了,但是思路基本没变,取其精华就好。
本书提出了一套性能黄金法则,优先级越高排名越前,一共14则:
1. 减少HTTP请求
2. 使用内容发布网络(CDN)
3. 添加Expires头
4. 压缩组件
5. 将样式表放在顶部
6. 将脚本放在底部
7. 避免使用CSS表达式
8. 使用外部JavaScript和CSS
9. 减少DNS查找
10. 精简JavaScript
11. 避免重定向
12. 删除重复的脚本
13. 配置ETag
14. 使Ajax可缓存
一般来说,使用外链的脚本和样式表更加有利。分别把外链脚本和样式表进行合并会减少HTTP请求,以节省客户端和服务器之间的通讯次数来加快页面打开速度。但是出于开发的便利,开发的时候一般会采取模块化的方式;这时候可以在部署前采用一些前端构建工具把这些模块文件合并起来再发布。
CDN是一组分布在多个不同地理位置的Web服务器,由于距离用户 物理距离比较短,所以能够更加有利于用户获取到静态资源;这种服务通常需要购买,也有一些免费、通用的CDN可使用,国内的可以使用BootCDN。
Expires头是用来告诉浏览器该相应的有效期,可以理解为该资源的“保质期”,在期限内可以使用该资源的缓存不需要重新请求。由于浏览器与服务器存在时钟同步问题,HTTP1.2.1还添加胡了Cache-Control和max-age来弥补Expires头的不足。通常用于脚本,样式表、图片等静态资源。
使用这种策略可能会遇到一个问题是,开发者可能想要在资源过期前这段时间更新它们。这时候,由于浏览器的缓存还没失效,这就需要通过更改文件名来令静态资源 强制失效。有很多种方式给静态资源打上版本号,可以一本正经地打上数字版本号,根据内容生成哈希码也行,甚至有人用π来给自己的资源打版本号(每次。
本书介绍的是gzip的方式压缩静态资源,实际上,这种方式会消耗额外的CPU资源。这种手段通常能够使文件大小减少70%。
如果把样式表放在底部时,浏览器会延迟显示任何可视化组件。另外,使用 CSS 的@import 等同于把想要加载的样式放在底部,所以不建议使用。对于浏览器的渲染机制,本书并没有过多提及,只是对现象做出了描述以及提供了解决办法。
如果样式表仍然在加载,构建呈现树就是一种浪费,因为在所有样式表加载并解析完毕之前无需绘制任何东西。否则,在其准备好之前显示内容会遇到 FOUC(无样式内容的闪烁,Flash of Unstyled Content)问题。
就是说,如果不把样式表放在 <head> 中,当遇到样式时,浏览器就会阻止页面呈现,等待样式表下载完毕。
如果把样式表放在底部,在 IE 中还会产生白屏现象。总之,把样式表放进<head>就能避免这些问题。
脚本对页面的影响是:
1)阻塞对其后面内容的呈现
2)阻塞后面组件的下载
3)浏览器会在下载脚本的时候阻塞并行下载,因为需要确保脚本能够顺序执行。
关于这点,这里有一篇讲解的比较深入的文章:
JS 一定要放在 Body的最底部么?聊聊浏览器的渲染机制
但是,实际开发中有时候很难完全遵守这条准则,那只能把能够放在最后的都放在最后。
使用CSS 的expression()通常会造成多次运算。实际上,需要用到CSS表达式的地方,通常能够找到其他替代方案,所以避免使用CSS表达式。
使用外部静态文件的优点有:
1)可以被浏览器缓存起来
2)组件可以重用
3)可模块化
4)能够被构建(合并压缩打版本)
...
缺点:
1)需要额外创建HTTP请求
...
简单来说,DNS 查找就是输入域名对服务器IP地址的查找过程。DNS缓存又分为浏览器DNS缓存、操作系统DNS缓存。当你输入www.google.com的时候,浏览器会先去自身的DNS缓存里面查找有没有google服务器的IP地址;如果找不到则继续到操作系统的DNS缓存查找;如果浏览器在这两个容器都没有找到google的IP地址记录,则会向广域域名体系查找。
减少JavaScript 文件大小的有几种手段,通常被广泛使用的是精简。精简就是去除JavaScript代码中的空格、注释等多余的字符,这种方式基本上没有什么缺点。
另外一种方式是混淆。混淆是在精简的基础上,把函数、变量名都用较短小的字符来替换,从而达到减少文件大小的效果。但是混淆会产生不少麻烦,很有可能会引入错误,虽然有利于防止逆向工程,当同时也增加了自己在线上环境调试的难度。
现在普遍的做法是发布前利用 Gulp、Grunt等自动化构建工具对资源进行压缩。
以下是一个重定向的过程:
浏览器发送请求 -- 服务器返回302 --服务器返回200 --浏览器开始呈现
就是说,在发送请求到返回200这段时间,页面完全是空白的;对比普通的请求多了一段空白时间。
重复的脚本对增加HTTP 请求次数和脚本执行的时间。
这个规则应该过时了,现在比较好的实践是直接根据内容给静态资源打上哈希版本号。
适用于以上的优化,大部分同样适用于Ajax请求。
总的来说,前端优化的总体思路是提高浏览器与服务器沟通的效率。
前端性能优化一味奉行“最佳实践”有时候反而过而不及,所以针对项目的实际情况来优化才是明智的选择。