web缓存大致可以分为:数据库数据缓存、服务器端缓存(代理服务器缓存、CDN缓存)、浏览器端缓存、web应用层缓存。
其中,浏览器端缓存的机制种类:HTTP缓存机制、浏览器本地存储(cookie 、localStorage、sessionStorage、webSQL、indexDB、……)如下图所示:
本文主要着重讲解浏览器本地存储(cookie 、localStorage、sessionStorage) 以及 HTTP缓存机制。
浏览器本地存储
Cookie
Cookie(或者Cookies),指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)
- cookie一般通过http请求中在头部一起发送到服务器端。
- 一条cookie记录主要由键、值、域、过期时间、大小组成,一般用户保存用户的认证信息。
- Cookie是为了解决HTTP无状态的特性而出现的,也可以叫用户识别机制。
sessionStorage 和 localStorage
Web Storage 是指 HTML5 的本地存储 API sessionStorage 和 localStorage。
- Web Storage 是通过存储字符串的 Key/Value 对来提供的,并提供 5MB (不同浏览器可能不同,分 HOST)的存储空间,如果数据存放超过最大限制会报错,并移除最先保存的数据。
- Web Storage 分为 sessionStorage 和 localStorage。localStorage 对象和 sessionStorage 对象使用方法基本相同,它们的区别在于作用的范围不同。sessionStorage用来存储与页面相关的数据,它在页面关闭后无法使用。而 localStorage则持久存在,在页面关闭后也可以使用。
Cookie VS Web Storage
Cookie VS Session
HTTP缓存机制
概述
从缓存位置上来说分为四种,优先级依次是:
Memory Cache、Service Worker Cache、Disk Cache、Push Cache
- MemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。浏览器秉承的是“节约原则”,我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。
- Service Worker, 是一种独立于主线程之外的 Javascript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。
- Disk Cache(HTTP Cache),Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。它又分为强缓存和协商缓存。
-
Push Cache,Push Cache 是指 HTTP2 在 server push 阶段存在的缓存。
- Push Cache 是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问 Push Cache。
- Push Cache(推送缓存) 是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。
- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。
浏览器端的缓存规则
- 新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下任一条件,浏览器会认为它是有效的,足够新的,而直接从缓存中获取副本并渲染:
- 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内
- 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度
- 校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
浏览器缓存的控制
分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
if (Cache-Control === no-store) {
// 绝对禁止缓存,浏览器本地不会进行缓存,每次请求资源都要从服务器重新获取。
直接向服务器获取资源(本地不缓存资源)
} else if (Cache-Control === no-cache) {
// 设置了 no-cache 之后并不代表浏览器不缓存,而是在获取缓存前要向服务器确认资源是否被更改。
每次请求都向服务器确认资源是否被更改(本地缓存资源),走下面的协商缓存
} else if (本地未过期 => max-age || Expires) {
使用浏览器本地缓存
} else { // 协商缓存
if (ETag有值) {
向服务器请求带if-None-Match
if (ETag与原来的一致) {
返回304,直接使用本地缓存
} else {
返回200,获取新资源
}
} else if (Last-modified有值) {
向服务器请求带if-Modified-Since
if (Last-modified与原来的一致) {
返回304,直接使用本地缓存
} else {
返回200,获取新资源
}
} else {
向服务器获取新资源,返回200
}
}
以上过程也可以被概括为三个阶段:
- 本地缓存阶段(也称强缓存):先在本地查找该资源,如果有发现该资源,而且该资源还没有过期,就使用这一个资源,完全不会发送http请求到服务器;
- 协商缓存阶段(也称弱缓存):如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期,则发一个http请求到服务器,然后服务器判断这个请求,如果请求的资源在服务器上没有改动过,则返回304,让浏览器使用本地找到的那个资源;
- 缓存失败阶段:当服务器发现请求的资源已经修改过,或者这是一个新的请求(在本来没有找到资源),服务器则返回该资源的数据,并且返回200, 当然这个是指找到资源的情况下,如果服务器上没有这个资源,则返回404。
详细信息——缓存有关的HTTP消息报头
Cache-Control
- max-age(单位为s)指定设置缓存最大的有效时间,定义的是时间长短。当浏览器向服务器发送请求后,在max-age这段时间里浏览器就不会再向服务器发送请求了。
- s-maxage(单位为s)同max-age,只用于共享缓存(比如CDN缓存)。比如,当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。也就是说max-age用于普通缓存,而s-maxage用于代理缓存。如果存在s-maxage,则会覆盖掉max-age和Expires header。
- no-cache 指定不缓存响应,表明资源不进行缓存,但是设置了 no-cache 之后并不代表浏览器不缓存,而是在获取缓存前要向服务器确认资源是否被更改。因此有的时候只设置 no-cache 防止缓存还是不够保险,还可以加上 private 指令,将过期时间设为过去的时间。(no-cache 绕开了浏览器:我们为资源设置了 no-cache 后,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期,即走协商缓存的路线)
- no-store 绝对禁止缓存,如果用了这个命令就是不会进行缓存,每次请求资源都要从服务器重新获取。(no-store 比较绝情,顾名思义就是不使用任何缓存策略。在 no-cache 的基础上,它连服务端的缓存确认也绕开了,只允许你直接向服务端发送请求、并下载完整的响应。)
Cache-Control与Expires
Cache-Control与 Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过 Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于 Expires。
public 与 private
public 与 private 是针对资源是否能够被代理服务缓存而存在的一组对立概念。如果我们为资源设置了 public,那么它既可以被浏览器缓存,也可以被代理服务器缓存;如果我们设置了 private,则该资源只能被浏览器缓存。private 为默认值。
Last-Modified与ETag
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
Last-Modified/ETag 与 Cache-Control/Expires
- 配置 Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;
- Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时, Cache-Control/Expires的优先级要高,即当本地副本根据 Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间 Last-Modified或实体标识 Etag了。
- 一般情况下,两者会配合一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时 Last-Modified/ETag将能够很好利用304,从而减少响应开销。
有Last-Modified为什么还要有ETag?
HTTP1.1中Etag的出现主要是为了解决几个 Last-Modified 比较难解决的问题:
- Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但 Last-Modified 却改变了,导致文件没法使用缓存
- 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
用户操作行为与缓存的关系
参考链接:
你必须懂的前端性能优化
浏览器缓存机制全攻略
Web缓存相关知识整理
9大浏览器端缓存机制分析
深入理解浏览器的缓存机制
推荐文章:
前端开发之走进Vue.js(入门知识点)
《你不知道的javascript上卷》摘要(上)
《你不知道的javascript上卷》摘要(下)
Git使用总结
如何在Vue+Webpack下配置Stylelint
Highchart属性笔记