引言
开发者来说,浏览器充当了重要角色。浏览器缓存(Brower Caching)是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。浏览器缓存的优点有: (1)、减少了冗余的数据传输,节省了网费, (2)、减少了服务器的负担,大大提升了网站的性能, (3)、加快了客户端加载网页的速度
浏览器缓存规则
强制缓存
不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回200的状态码,并且size显示from disk cache或from memory cache。from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况: •协商缓存生效,返回304和Not Modified 协商缓存生效 •协商缓存失效,返回200和请求结果 协商缓存失效
举例来说, C:S,你几岁了? S:C,我18岁了。 C:S,你几岁了?我猜你18岁了。 S:你知道还问我?(304) C:S,你几岁了?我猜你18岁了。 S:C,我19岁了。(200)
Respouse Headers的设置与缓存的关系
Respouse Headers缓存相关请求头与说明
下面通过demo,进行了了验证(由于第一次请求都是需要从服务器端获取数据,返回200,所以就不再贴验证图了) last-Modified
第二次请求数据未更改时:
第二次请求数据更改:
客户端第二次请求此URL时,根据HTTP协议的规定,浏览器会向服务器传送If-Modified-Since报头(HttpRequest Header),询问该时间之后文件是否有被修改过:如果服务器端的资源没有变化,则自动返回HTTP304(NotChanged.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
2、ETag工作原理
第二次发送请求:
(1)数据没有变化
服务端通过对比etag与if-none-match,数据未发生变化时,etag不会发生变化,服务端检测两者相等,返回304
(2) 数据发生变化
数据发生变化时,etag发生变化,服务端检测两者不相等,返回304,返回200
3、 Expires
第二次请求(在有效时间内):
第二次请求(不在有效时间内):
当请求在有效时间类,客户端会直接读取浏览器缓存,返回200(from disk cache),当不在有效时间内时,会进行协商缓存,返回304. 4、 Cache-Control
(在HTTP/1.0中可能部分没实现,仅仅实现了Pragma: no-cache),HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。常见有以下六个属性值:
(1) max-age
当max-age=0时,表示浏览器不会进行缓存,如下图所示:
第二次请求:
当max-age = 60,表示浏览器缓存60s
第二次请求(60秒以内):
第二次请求(60秒以外):
由此可看出,在60s以内,浏览器会直接从缓存中获取数据,超过60s,浏览器会重新从服务器端获取数据。
各请求头之间的关系
Cache-Control的no-cache与Etag或Last-Modified
no-cache控制客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。我们具体以实例来理解:
if (req.headers["if-modified-since"] === data) {
res.writeHead(304, "keep cache", {
"Content-Type": "text/plain",
"Cache-Control": "no-cache",
"last-modified": data,
//"Expires": datae
})
res.end();
} else {
res.writeHead(200, "OK", {
"Content-Type": "text/plain",
"Cache-Control": "no-cache",
"last-modified": data,
// "Expires": datae
})
}
第二次请求数据无变化:
第二次请求数据发生变化:
当Cache-Control设为no-cache时,需要使用协商缓存判断是否使用缓存,当第二次访问时,将Last-Modified与 if-modified-since进行比较,若相同,则使用协商缓存,返回304,若不同,则重新访问服务端,返回200。no-cache与Etag和Last-Modified是一样的原理,这里就不再实验。
Cache-Control的no-store与Etag或Last-Modified
no-store即所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存。
if (req.headers["if-modified-since"] === data) {
res.writeHead(304, "keep cache", {
"Content-Type": "text/plain",
"Cache-Control": "no-store",
"last-modified": data,
//"Expires": datae
"Etag": number
})
res.end();
} else {
res.writeHead(200, "OK", {
"Content-Type": "text/plain",
"Cache-Control": "no-store",
"last-modified": data,
// "Expires": datae
"Etag": number
})
第二次请求:
说明,当catch-control设为no-store时,浏览器不会在本地缓存数据,客户端每次访问都会向服务器请求数据。
关于 Cache-Control: max-age=秒 和 Expires
Expires = 时间,HTTP 1.0 版本,缓存的载止时间,允许客户端在这个时间之前不去检查(发请求) max-age = 秒,HTTP 1.1版本,资源在本地缓存多少秒。 如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖。
Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大,所以在HTTP 1.1版开始,使用Cache-Control: max-age=秒替代。
res.writeHead(200, "keep cache", {
"Content-Type": "text/plain",
"Cache-Control": "max-age=60",
//"last-modified": data,
"Expires": datae
// "Etag": number
})
第二次请求(60s之后):
可以看出,虽然Expires的期限未至,但max-age=60s,60s后,浏览器便会重新访问服务器,很明显,如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖。
关于 Cache-Control: max-age=秒 和Etag或Last-Modified
猜想:当max-age过期后,是否会判断etag或Last-Modified,进行协商缓存了?
我们以事实来说话:
if (req.headers["if-modified-since"] === data) {
res.writeHead(304, "keep cache", {
"Content-Type": "text/plain",
"Cache-Control": "max-age=60",
"last-modified": data,
//"Expires": datae
// "Etag": number
})
res.end();
} else {
res.writeHead(200, "OK", {
"Content-Type": "text/plain",
"Cache-Control": "max-age=60",
"last-modified": data,
//"Expires": datae
// "Etag": number
})
}
第二次请求(60s以内):
第二次请求,数据未发生变化(60s以外):
第二次请求,当数据发生变化时(60s外):
由此我们可以得到,当max-age的时间过期后,浏览器会进入协商缓存,通过比较last-modify与if-modified-since,若相等,则返回304,从缓存中读取数据,或不相等,返回200,从服务端读取数据。Etag原理与last-modify一样,这里就不再重复。
5、Cache-Control 与last-modify与Etag
讲到这里,可能我们会有个疑问,感觉etag与last-modify的原理一样,作用一样,为什么还会有两个了。我们就分析下这两者的区别: last-modify的缺点: 一些周期性修改的文件,修改时间变了但内容没变,此时不希望重新GET; 一些文件修改非常频繁,比如1秒内修改了多次,Last-Modified只能精确到秒; 一些服务器不能得到文件修改的精确时间; ETag是HTTP/1.1标准开始引入的,它是对Last-Modified的补充。 当etag与last-modify同时存在时,如果同时有 etag 和 last-modified 存在,在发送请求的时候会一次性的发送给服务器,没有优先级,服务器会比较这两个信息(在具体实现上,大多数做法针对这种情况只会比对 etag)。 我们还是通过实例来看具体情况:
if (req.headers["if-none-match"] == number) {
if (req.headers["if-modified-since"] === data){
res.writeHead(304, "keep cache", {
"Content-Type": "text/plain",
// "Cache-Control": "no-cache",
"Cache-Control": "max-age=60",
"last-modified": data,
// "Expires": datae,
"Etag": number
})
res.end();
}
} else {
res.writeHead(200, "OK", {
"Content-Type": "text/plain",
"Cache-Control": "no-cache",
"last-modified": data,
//"Expires": datae
"Etag": number
})
}
第二次访问数据无变化:
第二次访问last-modify有变化:
第二次访问etag变化:
同时改变就不演示了,其实大多数情况下,当我们内容改变时,通过相关插件监控,last-modify和etag都会改变,这样就会返回200,从服务器端读取数据。
6 、Cache-Control 与last-modify与Etag与Expires
通常Cache-Control 与 Last-Modified, Etag, Expire 是一起混合使用的,特别是 Last-Modified 和 Expire 经常一起使用。Last-Modified,Etag,Expires 三个同时使用时。先判断 Expire ,然后发送 Http 请求,服务器同时判断 last-modified和 Etag ,必须都没有过期,才能返回 304 响应。 Cache-Control —— 请求服务器之前 Expires —— 请求服务器之前 If-None-Match (Etag) —— 请求服务器 If-Modified-Since (Last-Modified) —— 请求服务器 我们同样通过实例来看:
(1)我们将cache-control设为no-cache,允许使用协商缓存,Expires设为2020年,保证不会到期,然后看下情况:
if (req.headers["if-none-match"] == number) {
if (req.headers["if-modified-since"] === data){
res.writeHead(304, "keep cache", {
"Content-Type": "text/plain",
// "Cache-Control": "no-cache",
"Cache-Control": "max-age=60",
"last-modified": data,
"Expires": datae,
"Etag": number
})
res.end();
}
} else {
res.writeHead(200, "OK", {
"Content-Type": "text/plain",
// "Cache-Control": "no-cache",
"Cache-Control": "max-age=60",
"last-modified": data,
"Expires": datae,
"Etag": number
})
}
第二次请求,数据不发生变化:
其实这里,如果只有expires时,应该不会向服务端发起请求,会出现200(from disk cache).当设置了cache-control为no-cache,会覆盖expires,表示需要进入协商缓存这一环节,若检测到数据没变化,则返回304.
(2) 我们将cache-control设为max-age=60,允许使用协商缓存,Expires设为2020年,保证不会到期,
然后看下情况:
第二次请求(60s以内)
由于还在max-age期限内,所以会直接读取缓存,返回200(from disk cache)
第二次请求(60s以后),数据未变化:
第二次请求(60s以后)当数据发生变化时:
60s以后,由于max-age过期,浏览器会向服务器发送信息,若数据未变化,返回304,若变化返回200和数据,由于请求了服务器,max-age会重新计时,又进入强制缓存。这里可以看出,expires与max-age功能一样,为什么还有会两个,其实expires是HTTP1.0中使用的,Expires要求客户端和服务端的时钟严格同步,HTTP1.1中引入Cache-Control来克服Expires头的限制。
讲到这里,其实它们之间的关系已经很明了了,做个总结:
Cache-Control —— 请求服务器之前 Expires —— 请求服务器之前 If-None-Match (Etag) —— 请求服务器 If-Modified-Since (Last-Modified) —— 请求服务器 当cache-control的max-age与Expires时间未过期时,不会向服务器发送请求,使用强缓存。 当cache-control设置为no-cache时,会使用协商缓存。 当cache-control设置为no-store时,不使用缓存,每次都会向服务器发送请求。 Cache-control是克服expires缺陷,HTTP1.1引入,优先级高于expires。 ETag是HTTP/1.1标准开始引入的,它是对Last-Modified的补充。
浏览器缓存与前端性能优化
首先分别对无缓存,协商缓存,强缓存进行9次请求,得出各自耗费的时间。很明显,强缓存所耗时间是最短的。无缓存即每次都需要发送http请求,服务端返回200和数据,协商缓存需要发送一次http请求,服务端进行判断,数据未变返回304,客户端读取浏览器缓存,变化返回200和数据,而强缓存是不发送http请求,客户端直接读取浏览器缓存。所以浏览器缓存与前端性能就主要围绕在是否发送http请求或发送http请求多少来进行优化了。 那么减少http请求有哪些方式了?
合并图片
CSSSprites直译过来就是CSS精灵,但是这种翻译显然是不够的,其实就是通过将多个图片融合到一副图里面,然后通过CSS的一些技术布局到网页上。特别是图片特别多的网站,如果能用css sprites降低图片数量,带来的将是速度的提升。
合并css/js
因为我们在浏览器中打开一个页面,页面中所需要用到的每一个css,js文件, 客户端都需要发送一次http请求到服务器端获取这些文件,所以在页面的设计时将浏览器一次访问需要的js,css合并成一个文件,这样可以减少http请求来提高网站性能。
充分利用强缓存
对于CSS,Javascript,Logo,图标等这些静态资源文件更新的频率都比较低,所以将这些静态资源文件使用强缓存,这样就可以减少http请求,从而提高性能。页面的初次访问者会进行很多HTTP请求,我们通过设置Expires头和Cache-Control的max-age,是这些组件能够缓存足够长的时间,这样第二次请求时就直接读取浏览器缓存,从而避免发送http请求,加载速度就会提高很多。 但是通过使用一个长久的Expires头,可以使这些组件被缓存,下次访问的时候,就可以减少不必要的HTPP请求,从而提高加载速度。在某些时候,静态资源文件变换需要更新到客户端的浏览器缓存中,这种情况下可以通过改变文件的文件名来实现,即使用浏览器缓存的网站如果需要更新CSS,JS,Logo等信息,可以通过改变文件名的方式进行更新。当我们的文件跟新时,我们最希望的是我们更改了哪里,对应的部分进行更新。这里我们就引入数据摘要要算法对文件求摘要信息,摘要信息与文件内容一一对应,就有了一种可以精确到单个文件粒度的缓存控制依据。目前成熟的解决方案:
FIS的解决方案
现在已经跟新到fis3了,fis3中封装工具方法,我们调用fis3进行版本发布,就会自动实现上述部署。 我们只需要在fis-confid.js中配置
fis.match('*.{js,css,png}', {
useHash: true
});
这样文件会在打包后的发布版本对应的文件添加唯一的hash值 可以看出,js/css/png等都被加上了md5 hash值,这样当我们版本中其中一个文件变化,hash才会变,从而保证浏览器可以读取更改的文件。Fis3跟webpack一样是个复杂的打包工具,这只是其中一个小部分。
webpack的解决方案
webpack通过对不同文件进行hash/chunkhash/contenthash的设置 对png|jpe?g|gif等的设置:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
}
对js的设置:
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
对css的设置:
filename: utils.assetsPath('css/[name].[contenthash].css'),
allChunks: true,
当我们打包时,对应的文件会有唯一标志符:
当有文件更改后,对应的hash值就会变化,浏览器就会向服务器重新请求该对应更改文件。
压缩组件
从HTTP1.1开始,Web客户端可以通过HTTP请求中的Accept-Encoding头来表示对压缩的支持Accept-Encoding: gzip,deflate.如果Web服务器看到请求中有这个头,就会使用客户端列出来的方法中的一种来进行压缩。Web服务器通过响应中的Content-Encoding来通知Web客户端。
下面我们来看看具体实例
Eg1:腾讯网 对于png等静态资源的设置:
通过对max-age的设置,是浏览器缓存时间为600s,600s之后便会使用协商缓存,这里主要通过last-modify进行比较。
对于css的设置:
对于css,腾讯网首先进行了打包处理,然后只设置了60s的缓存,60s之后再次请求便会进入协商缓存。
对于js:
对于js,同样进行了打包处理,然后设置了60s的缓存,60s之后再次请求便会进入协商缓存。
eg2网易首页: 对于png等静态资源:
网易首页将png等静态资源设置缓存时间为262800s,相当于3天左右的时间。在这期间,再次请求都会使用强缓存。
对于css的设置:
对css的设置,缓存时间达到315360000s,相当于10年,夸张了点。
对于js:
对于js的设置,将max-age设置为3600s,之后便会通过判断etag和last-modify进行协商判断,进入协商缓存。
不过,由于一个网站有非常多的png,js,css,所以对于特别的情况,需要特殊的设置。对于很少变化的静态文件,可以设置很长的缓存时间,例如10年。对于常变化的,就可以不舍缓存时间或很短比如60s。合理设置浏览器缓存将会大大提升网页加载速度,提升前端性能。
参考资料
浏览器缓存详解:blog.csdn.net/ywh147/arti…
http: baike.baidu.com/item/http/2…
node中文官网:nodejs.org/zh-cn/