如何使用缓存

缓存就是,在向后端获取资源时如果浏览器中有该资源的副本,就不用从服务器获取,直接使用副本。因此在前端优化中,缓存是必不可少的一环,他能显著的提升页面加载速度。(注意!!!,本文只针对chrome 91.0.4472.114。我并没有在其它环境测试)

对于前端来说,能够接触到的缓存主要分为两类,http缓存和浏览器缓存

  • http缓存
  • 强缓存
    • Pragma
    • Cache-Control
    • Expires
  • 协商缓存
    • ETag/If-None-Match
    • Last-Modified/If-Modified-Since
  • 浏览器缓存
  • Cookie
  • WebStorage
    • LocalStorage
    • SessionStorage
  • Service Worker (PWA)

Http缓存

强缓存

强缓存使用的是Pragma,Cache-Control,Expires三个(请求/响应)头字段。

在请求头中(requestHeader)

  • 当我们想完全关闭Http缓存时,只需要将PragmaCache-Control其中一个设置为no-cache都能达到效果。由于Cache-Control是Http1.1的请求头,为了兼容Http1.0,我建议将他们都设置为no-cache

!!! info
在chrome network中将disabled cache勾选时,所有请求的请求头都会自动携带上这两个字段。

  • 当我们刷新的时候,Chrome会自动在文档的请求头中增加Cache-Control: max-age=0,当然我们也可以在使用ajax发送http请求时将该字段手动加入请求头中。这样可以直接跳过强缓存,开始协商缓存。(注意请求头中max-age=设置其它时间没有用)。

!!! info
* 在firefox中,用户每次点击刷新页面,不但会给文档增加Cache-Control: max-age=0而且会给文档中的jscss外部文件增加Cache-Control: max-age=0
* 在safari中点击刷新不会设置Cache-Control: max-age=0,但是文档会自动跳过强缓存协商缓存(如果存在etag、last-modified。chrome和firefox是不会跳过协商缓存的。)

!!! warning
注意~请求头中的如果设置了这些字段Pragma: no-cache, Cache-Control: no-cache, Cache-Control: max-age=0那么不管响应头怎么设置,都无法在强缓存。并且只靠请求头是不能设置缓存的。

在响应头中 (responseHeader)

优先级
  • 首先,PragmaExpires是Http1.0中规定的通用首部,Cache-ControlHttp1.1规定的通用首部
  • 在响应头中包含了Cache-Control那么PragmaExpires都会失效。
  • 在只有PragmaExpires时,Pragma的优先级要高于Expires
响应头与请求头的区别

当我们在响应头中使用Pragma: no-cache, Cache-Control: no-cache我们一定要和在请求头中使用他们区别开。

  • 请求头中设置这两个字段表示关闭Http缓存。
  • 响应头中则表示每次请求该资源时,都要向服务器发送请求。(相当于强行协商缓存)。

那么响应头有没有请求头的完全关闭Http缓存的能力呢?当然是有的,在响应头中设置Cache-Control: no-store就可以关闭Http缓存。

缓存时间控制

强缓存时间控制,主要有两中方式Cache-Control: max-age=10Expires: Mon, 05 Jul 2021 09:02:14 GMT

  • Cache-Control: max-age=10表示从用户第一次收到响应时开始计时10秒内,如果用户再次请求相同url的资源,将返回浏览器中保存的副本。10秒后将重新向服务器发送请求。(就算选项卡关闭,浏览器关闭,10秒也会持续计时)
  • Expires: Mon, 05 Jul 2021 09:02:14 GMT表示在用户在第一次收到响应后,Mon, 05 Jul 2021 09:02:14 GMT之前,用户再次请求相同url的资源,将返回浏览器中保存的副本。

协商缓存

每次请求都会向服务器发出请求,如果要请求的资源没有发生改变,就返回状态码304,浏览器收到后,返回浏览器保存的副本。如果资源改变就返回200,和改变的资源。

Last-Modified/If-Modified-Since

  1. 当浏览器第一次向服务器发送请求,获得响应后,在响应头中可以拿到Last-Modified粒度为秒的资源最后修改时间。将之存放到浏览器自己的数据库中。
  2. 当浏览器再次向相同url的资源发起请求时,浏览器从数据库中去出Last-Modified将之放在请求头的If-Modified-Since字段中,一起发送给服务器。
  3. 服务器拿到If-Modified-Since与资源的最后修改时间做对比,如果相同就直接返回状态码304。如果不相同就返回状态码200,改变的资源,以及该资源的最后修改时间。重复过程1。

注意,如果想单独使用Last-Modified/If-Modified-Since来做协商缓存判断,必须要在请求头中加上Cache-Control: max-age=0。或者在响应头中增加Cache-Control: no-cache(也可以是max-age=0),不然会变成强缓存形式(请求会直接从disk cache、memory cache中取,而不向服务器请求。)。使用ETag就不会有这个问题。

ETag/If-None-Match

Last-Modified/If-Modified-Since的粒度为秒,如果一个文件在一秒内修改了多次,并且浏览器也请求了多次,服务器就会认为资源并没有发生改变,导致浏览器无法获取最新的资源,也有可能出现一个文件删除一行,又将被删除的行加回来,最终资源没有改变,但是修改时间却变了,导致浏览器向服务器重复获取了相同的资源。

ETag/If-None-Match很好的解决了这些问题。

Last-Modified/If-Modified-Since的步骤基本相同,只是在ETag中和If-None-Match中传递的是被请求资源的hash值。

!!! info 总结
* Last-Modified单独用需要加上响应头Cache-Control: max-age=0Cache-Control: no-cache或者请求头Cache-Control: max-age=0
* Etag可以直接用

浏览器缓存

cookie是一个4k的本地存储空间,它是以key=value字符串形式存储的。可以为每条cookie设置属性

cookie通常用来验证登陆状态(cookie/session)和保存用户个性化配置。

设置Cookie的方法

  1. 可以在前端通过document.cookie来设置cookie
  2. 可以在后端通过Set-Cookie这个响应头设置cookie

WebStorage

SessionStorage

sessionStorage属性允许你访问对应当前源的sessionStorage对象,存储在sessionStorage中的数据会在页面会话结束时,被清除。(每一个标签页算一个页面)

  • 页面会话在浏览器打开期间会一直保持,并且重新加载(刷新)和恢复页面后也会保持原本的会话。
  • 打开多个相同URL的标签页,每个标签页的sessionStorage都是独立的。相同标签页下的同源不同URL页面间也是独立的
  • 关闭对应的标签页,会删除对应标签页的sessionStorage。
  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文(这句话是mdn上的,我不是很理解。。我的理解是使用window.open()打开的同源的新标签页。或者直接在该标签页顶部的url处修改为同源的其它页面url。或者使用a标签的默认跳转。都会把该页面的会话的上下午复制到新会话的上下文)。

这里列几个api哈😂

1
2
3
4
5
6
7
8
9
10
11
// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value');

// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');

// 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key');

// 从 sessionStorage 删除所有保存的数据
sessionStorage.clear();

localStorage

localStorage允许你访问对应当前源的localStorage对象,存储在localStorage中的数据不会在页面会话结束时被清除。

这里要讲一下cookie,localStorage和sessionStorage的一个区别,cookie和localStorage是标签页间或同一标签页的同源不同URL页面间共享的,而sessionStorage是绝对独立的。

Service Worker (PWA)

这是个实验中的功能,在mdn中可以看出这种缓存多用于PWA应用。下面就用一个简单的小demo讲一下,就不细说了。

首先编写好worker代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// worker.js
"use strict";
// worker安装时触发
self.addEventListener('install', (e) => {
// 如果有worker更新了,强制更新worker
self.skipWaiting();
// 直到这个promise完成了,才开始接下来的监听(fetch)
// 获取列出的资源。
e.waitUntil(caches.open('fox-store').then((cache) => cache.addAll([
'image.png',
'index.html'
])));
});
self.addEventListener('fetch', (e) => {
// 监听资源获取,判断是否缓存,如果缓存了就直接返回缓存内容,反之fetch
e.respondWith(
caches.match(e.request).then((response) => { console.log(response); return response || fetch(e.request) }),
);
});

然后注册worker

1
2
3
4
if ('serviceWorker' in navigator) {
// 不能用file://协议,我这个是放在静态服务器上的。
navigator.serviceWorker.register('/public/worker.js');
}

参考文章

一文搞懂http缓存
MDN