前端浏览器缓存原理实战应用
前言
浏览器缓存原理是前端面试中必问的一道经典题目,也是前端开发必须掌握的知识。本文介绍浏览器缓存在实际项目上的应用,希望有缘看到这篇文章的小伙伴,不止是准备面试,而是真的掌握这一知识点。如果你比较懒,看懂文章的简单概括揣摩一下就可以当面试题的答案。如果你想要多了解一下,不妨看完文章。
简单概括强缓存和协商缓存
强缓存跟协商缓存说的是浏览器请求静态文件(js、html、css、图片以及其它文件)缓存的使用策略,作用是提高页面加载速度。
浏览器使用缓存的过程是:先执行强缓存,查看本地有没有这么一个文件,文件的状态(expires和cache-control)是不是过期了,没有过期就直接用本地缓存不向浏览器发送请求,如果过期了就执行协商缓存,到服务器询问一下能不能继续用缓存(last-modified和etag),能就继续用本地的缓存,不能就加载新的文件。
缓存设置优先级是:
Cache-Control > expires > Last-Modified|etag
强缓存的状态码是 200(from memory cache)
协商缓存的状态码是304
实际应用
强缓存
从前面的简单概括来看,一旦设置了过期时间,浏览器就不会在没过期的时候向服务器发送请求,这就会导致你服务器实际上更新了文件,但是浏览器不知道,所以强缓存一定要慎重使用。
html是必须加上不使用强缓存配置的,不然你可能需要客户清理浏览器缓存才能看到你的代码更新
下面是html配置,
<head>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" CONTENT="no-cache">
<meta http-equiv="expires" content="-1">
</head>
但是上面的meta加上了之后可能也没用,这个时候你要看看服务器端的配置了,这里只说nginx
location ~ .*\.(htm|html )$ {
add_header Pragma "no-cache";
# 60秒内不会访问服务器
# add_header Cache-Control "max-age=60";
# 不缓存
add_header Cache-Control "no-cache";
add_header expires -1;
}
#过期时间为30天示例
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
expires 30d;
}
# 时间设置永不过期示例
location ~ .*\.(js|css)$ {
expires max;
}
其实js、css跟图片这些资源,webpack打包的时候会给文件名加随机数(hash),每次更新页面,其实访问的是新的静态文件,所以我们在使用的时候采用的策略是永不过期。可以看看dist文件夹
强缓存基本上运维加webpack都给做好了,所以对缓存不是很懂的小伙伴不太感受得到自己实际上已经在使用他们。
但是一旦你的服务器跟html上面都没配置,那浏览器会自己判断使用自带的配置,什么都不配置的结果就是缓存时间不可控,性能差。
协商缓存
协商缓存更多用于静态文件服务器(如cdn),属于主动优化的一部分,需要先配置不使用强缓存,或者强缓存有时限。
举个例子,我们每次打包项目,图片都加上了hash随机数,导致了每次更新版本,图片都得重新加载,但是其实很多时候我们的图片并不会轻易变化。这个时候图片存到单独的静态文件服务器或者是cdn上会更好一些,但是我们也要保证图片真的需要更新的时候能被更新到,所以这个时候协商缓存就派上用场了。(c端项目更推荐cdn,有兴趣可以搜搜cdn相关的文章,作者以后也会写)
协商缓存主要是服务器端的配置,这里介绍nginx
# 开启Last-Modified方式二
location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
# 开启关闭etag,有些版本默认开启有些默认关闭,按需配置
etag off|on;
# 开启Last-Modified方式一,默认是开启的 on
if_modified_since off|on;
# 自定义Last-Modified的时间可以手动触发刷新,要配合if_modified_since off, 慎用
add_header Last-Modified 'Fri, 12 May 2006 18:53:33 GMT';
# 不想要就置空
# add_header Last-Modified "";
add_header Cache-Control "public";
# 设置不用用强缓存
expires -1;
}
实际上协商缓存nginx1.7.3及以上的版本也默认开了,所以,你只要确保需要使用协商缓存的文件没有被强缓存就行了。
Last-Modified不需要自己设置时间,nginx会去查文件修改日期。
etag是更新静态文件会打一个标记(tag),是一串nginx自带算法生成的字符串,长这样
etag:“50b1c1d4f775c61:df3”
Etag 是http版本迭代对 Last-Modified的优化补充,主要为了解决:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
如果Last-Modified没起作用,试试把Etag 打开
优先级
因为强缓存的优先级高于协商缓存,所以有下面的属性优先级关系
Cache-Control > expires > Last-Modified|etag
服务器收到请求则会先判断资源的 last-modified,再判断 etag ,必须都没有过期,才能返回 304,所以它们是同时生效的
总结
nginx一般都是运维配置好了,而html上的meta是需要前端同学手动添加的。所以一旦缓存出了问题,你就去看html设置了缓存没,再问一问运维同事看看nginx的相关配置是怎样的。
附录 缓存属性取值说明
html中的meta跟服务器的header属性取值是一样的,这里补充一下避免使用的时候还得翻文档
Cache-Control
- Public指示响应可被任何缓存区缓存。
- Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。
- no-cache指示请求或响应消息不能缓存
- no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。
- max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。
- min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。
- max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。
Last-Modified/If-Modified-Since
- Last-Modified/If-Modified-Since要配合Cache-Control使用。
- Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。
- If-Modified-Since:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
expires
- -1指的是1秒前已经失效了
- max 通常理解为永不失效(官网文档其实是到“Thu, 31 Dec 2037 23:55:55 GMT”)
- 1d、1M指具体1天、1个月的时间(官网的 Configuration file measurement units)