以下内容来自于《高性能网站建设指南》一书,书中写了14条规则,有些规则被我合并了,有些在目前来看已经不适用了,最后总结出这10条规则,之后会继续写《高性能网站建设进阶指南》
1、减少HTTP请求
- CSS Sprites:图片精灵,将多张图片合并成一张图片,使用background-position来进行定位显示图片
- 内联图片,通过使用data:URL的模式可以在Web页面中包含图片但无需任何额外的HTTP请求,比如可以将小图片转化为base64的格式,然后赋值给src,但如果图片过大,不建议转化,因为会加大页面的返回数据。使用这个方法带来的问题的图片不会被缓存
- 合并css和js文件
2、使用内容发布网络:CDN
CDN是一组分布在不同地理位置的Web服务器,它可以使用户访问到离自己最近的服务器,从而加快响应速度
3、压缩组件
可以使用gzip编码压缩响应包来加快响应速度。
web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持。
Accept-Encoding:gzip,deflate
如果服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来压缩响应,web服务器通过响应中的Content-Encoding头来通知客户端
Content-Encoding:gzip
但是压缩也会有成本,服务器端压缩会花费额外的CPU周期来完成压缩,客户端要对压缩文件进行解压缩,所以通常对大于1KB或2KB的文件进行压缩,mod_gzip_minimum_file_size指令控制着希望压缩的文件的最小值,默认为500B
如果有使用代理服务器,那使用gzip压缩有个严重的问题
假设一个不支持gzip的浏览器发送了一个请求到代理服务器,这是代理第一次接收到这个请求,因此还没有缓存,然后代理服务器转发给web服务器,此时服务器的响应是没有经过压缩的,而代理服务器就把这个未经过压缩的缓存起来了,然后一个支持gzip的浏览器请求了相同的URL,此时代理服务器就直接返回了未经过压缩的内容,更糟糕的是,如果反过来,先来了一个支持gzip的请求,代理缓存了一个压缩过的响应,然后来了一个不支持gzip的请求,此时返回经过压缩的响应就会报错。
要解决这个问题可以在web服务器的响应头中添加Vary头,告诉代理服务器根据一个或多个请求头来改变缓存的响应,所以代理服务器会缓存多个版本,因为控制缓存的是Accept-Encoding响应头,所以Vary的响应头应该包含Accept-Encoding
Vary:Accept-Encoding
4、将样式表放在顶部
- 白屏和无样式内容闪烁(FOUC):
浏览器对html文件是边解析边呈现的,如果将样式表放在页面底部会导致浏览器阻止内容逐步呈现。
如果样式表仍在加载,构建呈现树就是一种浪费,所以在所有样式表加载并解析完毕之前无需绘制任何东西。如果把css放在底部,每个浏览器的处理方式不一样,有些会出现白屏,有些会出现FOUC。
如果是为避免当样式变化时重绘页面中的元素,浏览器等待所有样式表都下载完成了才会开始绘制页面,就会出现白屏(如IE在新窗口加载页面、重新加载和作为主页时)
如果是在样式表加载之前就呈现内容,就会出现FOUC(如IE在点击链接、使用书签或键入URL时,Firefox也是)
- @import
在css中通过@import引入其他的样式表时,@import规则会导致组件下载时的无序性。即便是css放在了HEAD头部里,@import引入的样式表也可能最后才会下载。
5、将脚本放在底部
浏览器解析DOM的时候,如果遇到script标签,它会先执行script里的内容,如果是外部的js文件,它会停止解析,而先去下载js文件,因此如果将脚本放在顶部会阻塞它后面的内容的逐步呈现,而且还会阻塞它后面的组件的下载
- 并行下载
浏览器可以并行地执行HTTP请求,在HTTP1.1中,IE和火狐默认每个主机名可以并行下载两个组件。
并行下载可以提高组件下载效率,因为浏览器对并行下载的数量是针对每个主机名的,所以我们可以通过CNAME(DNS别名)来将组件分别放到多个主机名中,以提高并行下载数量。但增加并行下载数量也会增加开销,这取决于用户的带宽和CPU速度,过多的并行反而会降低性能,一般情况下,使用两个主机名是比较好的选择,过多的主机名也会增加DNS查找的时间(下面会介绍DNS)。
- 脚本阻塞下载
在下载脚本时,并行下载是被禁用的,即使使用了不同的主机名,其原因有二:
a、脚本有可能使用document.write来修改页面内容
b、为了保证脚本能够按照正确的顺序执行
所以为了不阻塞其他组件的下载,最好将脚本放在页面底部
- 异步加载脚本:async和defer
script标签拥有defer和async属性,他们都可以实现脚本的无阻塞加载,但它们有一定的区别:
defer:浏览器会异步下载该文件,不会影响后续DOM的渲染,如果有多个script标签设置了defer,则会按照引入的顺序执行所有的script,defer脚本会在文档渲染完毕后,在DOMContentLoaded事件调用前执行。
async:浏览器会异步下载该脚本,当下载完成时直接执行,而不会按照引入顺序来执行
6、使用外部JS和CSS
纯粹而言,使用内联更快一些,因为使用外部的js和css会增加HTTP请求,但是使用外部JS和CSS,我们可以使用缓存技术,而且可以更好的复用组件。为了得到更好的性能,我们可以同时使用内联和外部js、css
对于需要快速展现给用户的首页,我们可以使用内联技术(像百度和谷歌的首页内容非常少,很适合用内联技术),同时在主页加载完成后,可以动态下载后续页面会用到的外部文件,这能够将外部文件放到浏览器的缓存中以便用户接下来访问其他页面。
window.onload = function(){
setTimeout(function(){
downloadComponents;
},1000); //延迟1s执行,保证页面的加载完成了再加载后续页面需要的外部文件
}
function downloadComponents(){
downloadJs('js的地址');
downLoadCss('css的地址')
}
function downloadJs(url){
var elem = document.createElement('script');
elem.src = url;
document.body.appendChild(elem);
}
function downLoadCss(url){
var elem = document.createElement('link');
elem.rel = 'stylesheet';
elem.type = 'text/css';
elem.href = url;
document.body.appendChild(elem);
}
如果直接在主页中加载这些外部组件,可能会影响到主页的样式或交互,所以通常把这些组件放在一个不可见的IFrame中
7、减少DNS查找
Internet是通过IP地址来查找服务器的,但IP不利于记忆,所以我们一般使用域名来访问服务,当我们在浏览器中访问一个服务器时,浏览器会先查看操作系统中的host有没有设置该域名对应的ip,如果没有就会请求一次DNS域名服务器,以获取到要访问的域名所对应的IP地址(DNS服务器相当于电话本,一个人名对应了一个或多个电话号码)。
- DNS缓存和TTL
浏览器和操作系统都可以缓存DNS。查找返回的DNS记录包含了一个存活时间值(Time-to-live,TTL),该值告诉客户端可以对该记录缓存多久。
浏览器对缓存的DNS记录的数量是有限制的,如果用户访问了很多具有不同域名的网站,较早的DNS记录会被丢弃。
HTTP的Keep-Alive特性所扮演的角色也很重要,一个持久的TCP连接将会一直使用,直到其空闲1分钟为止,由于连接时持久的,所以无需进行DNS查找
8、避免重定向
重定向用于将用户从一个URL重新路由到另一个URL,当web服务器向浏览器返回一个重定向时,其响应码为3XX(304不是重定向,它表示请求的资源未过期,可直接使用缓存)。
9、使AJAX可缓存
对于实时性要求不高,或数据基本上不会变化的请求,可以通过设置Cache-Control响应头来控制它的缓存策略。
或者可以将第一次请求到的数据保存在本地,也可以避免多次请求
10、合理使用缓存策略
通过Cache-Control响应头可以控制组件或请求的缓存策略,关于缓存请看这里