HTTP 概述
GET 请求包含一个 URL 和 HTTP 响应(状态码、响应头、响应体)。如果浏览器和服务器支持,可以使用压缩来减少响应的大小。浏览器可以使用 Accept-Encoding 头来声明它支持压缩,服务器使用 Content-Encoding 头来确认响应已被压缩。
浏览器会自动对资源进行缓存,如果浏览器不确定缓存是否有效,就是生成 GET 请求使用 If-Modified-Since 头将最后修改的日期(最后修改日期基于响应中 Last-Modeified 头参数)发给服务器,如果资源没有变化服务器会返回 304 Not Modified
状态码不再发送响应体。
Expires 头通过制定资源的缓存有效时间消除浏览器缓存有效性的效验。
Connection: Keep-Alive
开启持久连接,通过 Connection: Close
头来关闭。
CDN
CDN 的原理是尽可能的在各个地方分布机房缓存数据,这样即使我们的根服务器远在国外,在国内的用户也可以通过国内的机房迅速加载资源。
因此,我们可以将静态资源尽量使用 CDN 加载,由于浏览器对于单个域名有并发请求上限,可以考虑使用多个 CDN 域名。并且对于 CDN 加载静态资源需要注意 CDN 域名要与主站不同,否则每次请求都会带上主站的 Cookie,平白消耗流量。
缓存
通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。
强缓存
强缓存可以通过设置两种 HTTP Header 实现:Expires
和 Cache-Control
。强缓存表示在缓存期间不需要请求,state code
为 200。
Expires
Expires: Wed, 22 Oct 2018 08:41:00 GMT
Expires
是 HTTP/1 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT
后过期,需要再次请求。并且 Expires
受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-control
Cache-control: max-age=30
Cache-Control
出现于 HTTP/1.1,优先级高于 Expires
。该属性值表示资源会在 30 秒后过期,需要再次请求。Cache-Control
可以在请求头或者响应头中设置,并且可以组合使用多种指令
多种指令配合流程图
从图中我们可以看到,我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
协商缓存
如果缓存过期了,就需要发起请求验证资源是否有更新。协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified
和 ETag
。
当浏览器发起请求验证资源时,如果资源没有做改变,那么服务端就会返回 304 状态码,并且更新浏览器缓存有效期。
协商缓存
Last-Modified 和 If-Modified-Since
Last-Modified
表示本地文件最后修改日期,If-Modified-Since
会将 Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来,否则返回 304 状态码。
但是 Last-Modified
存在一些弊端:
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成
Last-Modified
被修改,服务端不能命中缓存导致发送相同的资源 - 因为
Last-Modified
只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
因为以上这些弊端,所以在 HTTP / 1.1 出现了 ETag
。
ETag 和 If-None-Match
ETag
类似于文件指纹,If-None-Match
会将当前 ETag
发送给服务器,询问该资源 ETag
是否变动,有变动的话就将新的资源发送回来。并且 ETag
优先级比 Last-Modified
高。
以上就是缓存策略的所有内容了,看到这里,不知道你是否存在这样一个疑问。如果什么缓存策略都没设置,那么浏览器会怎么处理?
**
对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date
减去 Last-Modified
值的 10% 作为缓存时间。
实际场景应用缓存策略
单纯了解理论而不付诸于实践是没有意义的,接下来我们来通过几个场景学习下如何使用这些理论。
频繁变动的资源
对于频繁变动的资源,首先需要使用 Cache-Control: no-cache
使浏览器每次都请求服务器,然后配合 ETag
或者 Last-Modified
来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
代码文件
这里特指除了 HTML 外的代码文件,因为 HTML 文件一般不缓存或者缓存时间很短。
一般来说,现在都会使用工具来打包代码,那么我们就可以对文件名进行哈希处理,只有当代码修改后才会生成新的文件名。基于此,我们就可以给代码文件设置缓存有效期一年 Cache-Control: max-age=31536000
,这样只有当 HTML 文件中引入的文件名发生了改变才会去下载最新的代码文件,否则就一直使用缓存。
Service Worker
Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch
函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
CSS 精灵(CSS Sprites)
将多张图片合并在一张图中,使用 CSS 的 background-position 属性将 HTML 元素移到期望的图片上。
优点
- 减少图片的字节
- 减少了网页的 HTTP 请求,从而大大的提高了页面的性能
- 解决了网页设计师在图片命名上的困扰,只需对一张集合的图片上命名就可以了,不需要对每一个小元素进行命名,从而提高了网页的制作效率。
- 更换风格方便,只需要在一张或少张图片上修改图片的颜色或样式,整个网页的风格就可以改变。维护起来更加方便。
内联图片
使用 Base64 字符串来代替图片的 URL 属性,如下所示,以 data:image/png;base64
开头。
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFgAAAAwCAYAAACVMr0DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyMThFN0FFOTYzNDAxMUU0Qjg0MEY0MTgyQzZENjY3MSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMThFN0FFQTYzNDAxMUU0Qjg0MEY0MTgyQzZENjY3MSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjIxOEU3QUU3NjM0MDExRTRCODQwRjQxODJDNkQ2NjcxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjIxOEU3QUU4NjM0MDExRTRCODQwRjQxODJDNkQ2NjcxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+pUgM2QAABbxJREFUeNrkW01sG0UUnqQrBWHamEqt1FKIkZBSCWH7UA49lDicuAC+U6mGA1LFoemhF6gU9wASJ3IA5YS6UYMEJxLgwImu20MOjYQdCSmHStj8RWpRsd0fEakonRfeVpPdt7Mz3tl4J33Sar2zv/727fe+92ZmZGtri9lqK7lima/yw3yGk/fXPNn+EdsA5qACoHW+1PgynoFH6vFlCZ6Jg922GmAObpWv3IwAS9m7HGRXbBi1CNwKX31rGtxc8Tg7cOpVU5e7zJ+zZp0HIy20TYILwE5+M8fGXji6vf1f/x67+f5Fduf7n0xc/kWfLmzx4BnTniuCC7bvwDNs8mveNnHUxOXrUg9+6cjhKv4p0dybG7dc4ZgCBhrf2uJ+4prwiVfENn58XdGDwRsmTIELlPDyj1+S+/74ZJ79/vG8ids8y72460jeQInwItEA4Flhu4EBSPZWp6LedAw9TDD7DCSk5xCeVibA7fDlZ77P356WeKonANngXlqJOLSh8aBG7cHa+jbnAi2ENNe1VaP3Go3gu6DNGXqjorWH5VoPe3fZrxc+DbXf/uo71r9+w+i9HIInzxBCGj79zwa9CfJ1MEgVeDtFEVIuN2W3F5fZZucvNv7aie3tf/lvaDNtjoKnzvE/3BXoYRCjaGKK4GQVLieDlg/UoPYUVw/Pf3RW6xyVYOgIXuYS3NtSjfQDAGzMANxjH57ddapRAXhUAJeihmrSh+DXzpu4jq3mcABmIsCtcO8dJBDlie2oIFkLSDBtesg8wAQgLfjjHNym5DwKsDJ/Wc0AzbTxJdUlyYcIsGcqwIEiuHVlORMAi14LAa0OlMGXM5LzShQVEu1NBFHM3jyJtxuzzc6fUslFBcY0lISDevQ8psJdbK8SXl3SvLYv73YUaTjg0wLIoReyW55FBcb+9VXzAAc/SaxDBDXrkgbALQSqjvKuGSHHKOvuuSCnmMm5CeRaOwAwUIaXRTDGNLQwpNQqWZ9DeO8UIfzzWGPQtSaREheEmkemPBjKl+p6el4PYNSrboRiyGt85nGKoxAV4GKUi70UgeB6jC5qJ/WqNpEiU8WfoRtU2O631pWOBcWhmmiU0XNLMcGnMQhFgFcG6xh4zwJBRUM1APeXN94z7sHVOIWAn26SekIjQDFllqHy5W6rCFldQSfQzQic2iSUxBMHcAfXsu4ZnUAnBjF4MeeEbUpre3sVYA9rAB56aBr9Xx6RVjNJCm3EZJ2b9PEnYChU7HEruaJWJpe652BGJ0u3l9ketaTjIi5x8EZgUTh2acB9T06QS2gA4uywAd78Tb1iNjbxHDv0zlt2AIx6mKKJBaGKZ8yiut+ho1N1YAlweFKAUx06BRkiX2oxSiGx9z5dPB7Oynp39wQHy6yK2raGYBfYzqFWvtUT89z4/nBWtrZuF8BYr9CpU5wLyDE3otZRihgfoSWvRLvzw1UrVQSVKjcVX85MTJIyi6VSbTv45uuhNkNDUHddRQQB6Chq6L9ZeFTQAq7Ffj/oB6zoliyPfHB6J/f277F/JADvy+9XGnDt8OOOEcV3uL5xgCOGVKmMV7vAl4uEavB5WRxoCPQB2WRZdbjAodNvh+gBBlE/lAS43CuTWtld0q9DlSKCkR5G/MgAhg5PGIG5GuDdhg+uQDutQApdUH34wxxg0aCrPm162Ph8MRWKEEFqMXnpEkqTVUyPK5TnBlLoCsq3qDRaiTIAXPDeoFSDnmITBl8FJCi66kQ30XgMXoTX1iM8OwSuCDL7f9CKS9AQFF+6K7kiVPrIIpRsbgWAYbqArmFNHYBl4PkWxZ3nY87zga5hFz9lLpVmb3yxyPrXbkg5d0i2AI4BP5RmGYEGTiOdVbU0ZhmlbI9nGVkzERHnyV214FF3TEa0ZiIizgmeRrrKovWYzTM9BZBBxl3KENA9TJzKQXCtoogI2hj2bPsuB1UqI60G2AZ7JMAA5DJcXbfGMfsAAAAASUVORK5CYII=">
为何要使用 Base64 编码图片
- 使用 Base64 编码可以减少网络请求
- 将图片转为字符串后,图片文件会随着 HTML 元素一并加载,这样就可以减少 HTTP 请求的次数
- 采用 Base64 编码的图片是随着页面一起加载的,不会造成跨域请求问题
- 没有清理图片缓存的问题
Base64 图片的弊端
- Base64 编码后生成的字符串大小会增加约 30%,增大 HTML/CSS 体积,而且可读性差
- IE8 不兼容,由于 IE8 以下不支持 data url 格式
使用场景
- 图片的尺寸很小,且很少被更新
- 存在跨域限制的地方
- 不想页面缓存的图片
合并脚本和样式
合并脚本和样式会减少 HTTP 请求的次数。但是将它们合并在一起,会造成代码不清晰。在上线部署时,将同一个页面的脚本和样式进行合并。
压缩资源
通过 HTTP 请求中的 Accpt-Encoding 头表示对压缩的支持。
压缩什么:压缩脚本、样式和 HTML 文档。
节省:数据传输内容减少。
代理压缩
向 Web 服务器响应中添加 Vary: Accept-Encoding
。当浏览器带着 Accpt-Encoding:gzip
访问代理时,它接受到的是压缩过的内容。没有 Accpt-Encoding
头的浏览器收到的就是未经压缩的内容。
压缩前端打包代码
$ npm install compression-webpack-plugin -D
然后在 vue.config.js
中加入
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const productionGzipExtensions = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
configureWebpack: config => {
const plugins = [];
// Begin 生成 gzip 压缩文件
plugins.push(
new CompressionWebpackPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExtensions,
threshold: 10240,
minRatio: 0.8
})
);
// End 生成 gzip 压缩文件
config.plugins = [...config.plugins, ...plugins];
}
}
Nginx
$ vim /usr/local/nginx/conf/nginx.conf
gzip on; # 开启Gzip
gzip_min_length 1k; # 不压缩临界值,大于1K的才压缩,一般不用改
gzip_buffers 4 16k; # 默认就好
#gzip_http_version 1.0; # 默认就好
gzip_comp_level 2; # 压缩级别,1-10,数字越大压缩的越好,时间也越长,根据自己的需求更改
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 进行压缩的文件类型,缺少什么类型加上,JavaScript有两种写法都写上.
gzip_vary off; # Squid等缓存服务
gzip_disable "MSIE [1-6]\."; # IE6对Gzip不友好,忽略IE6
重新加载 nginx
$ sudo /usr/local/nginx/sbin/nginx -s reload
查看相应页面的 DevTools
是否有响应头 Content-Encoding: gzip
,如果有则代表 Gzip
成功。
参考
【1】高性能网站建设指南:前端工程师技能精髓
【2】前端面试之道
【3】Nginx开启Gzip压缩大幅提高页面加载速度