缓存可以简单高效的优化性能,可以显著减少网络传输带来的损耗。浏览器缓存可以帮我们在数据请求中的网络请求和浏览器响应这两步优化性能,直接使用缓存而不发起请求,或者发起请求但是后端存储的数据和前端一致,就没有必要将数据返回回来,就减少了响应数据。

1. 缓存位置

Service Worker

       它是浏览器背后的独立线程,一般用来实现缓存功能。传输协议必须是 https,因为涉及到请求拦截,必须用 https 协议来保障安全。
       实现缓存功能步骤:

  1. 注册 Service Worker
1
2
3
4
5
6
7
8
9
10
11
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register('sw.js')
.then(function(registration) {
console.log('service worker 注册成功')
})
.catch(function(err) {
console.log('servcie worker 注册失败')
})
}
  1. 监听到 install 事件,缓存需要的文件
1
2
3
4
5
6
7
8
9
// sw.js
// 监听 `install` 事件,回调中缓存所需文件
self.addEventListener('install', e => {
e.waitUntil(
caches.open('my-cache').then(function(cache) {
return cache.addAll(['./index.html', './index.js'])
})
)
})
  1. 下次用户访问就通过拦截请求查询是否存在缓存,存在则直接读取文件,否则就去请求数据
1
2
3
4
5
6
7
8
9
10
11
12
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response
}
console.log('fetch source')
})
)
})

       在开发者工具中的 Application 可以看到 Service Worker 已经启动,Cache 中也可以看到我们缓存的文件。

Memory Cache

       Memory Cache 是内存中的缓存,读取速度比磁盘快,但是缓存持续时间短,随着进程的释放而释放,一旦关闭 tab 页,内存中的缓存就会被释放。
       但是不能让数据都存在内存中,计算机内存比硬盘容量小,大文件一般不存储在内存中,内存如果使用率高,文件会优先存储进硬盘。

Disk Cache

       Disk Cache 是存储在硬盘中的缓存,虽然速度较慢,但是比 Memory Cache 胜在容量和存储时效性上。它会根据 HTTP Header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。即使跨站点,相同地址的资源一旦被硬盘缓存下来就不会重新请求数据。

Push Cache

       Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时才会使用,缓存时间短暂,只在 session 中,一旦 session 结束就会被释放。

2. 缓存策略

       浏览器缓存策略分为强缓存和协商缓存两种,都是通过设置 HTTP Header 来实现的。

强缓存

       强缓存表示在缓存期间不需要请求,state code 为 200。

  1. 设置 Expires
1
Expires: Wed, 22 Oct 2018 08:41:00 GMT

       表示会在特定的时间后过期,需要再次请求。但是 Expires 受限于本地时间,修改了本地时间可能会造成缓存失败。

  1. 设置 Cache-control
1
Cache-control: max-age=30

       表示资源会在 30s 后过期,需要再次请求,优先级高于 Expires,可以在请求头或者响应头中设置,可以组合使用多种指令。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
常见指令作用

协商缓存

       协商缓存表示如果缓存过期了,需要发起请求验证资源是否有更新。当浏览器发起请求验证资源时,如果资源没有改变则返回 304,并且更新浏览器缓存有效期。

  1. 设置 Last-Modified
           Last-Modified 表示本地文件最后修改地址,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话则讲新的资源文件发送回来,否则返回 304。
           但是如果本地打开了缓存文件,会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源。Last-Modified 以秒计时,如果在不可感知的时间内修改完文件,服务端则认为资源命中,不会返回正确的资源。
  2. 设置 ETag
           ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问 ETag 是否改变,有变动就发送新资源回来,并且优先级高于 Last-Modified。

       如果没有设置任何缓存策略,浏览器会采用一个启发式的算法,通常取响应头中的 Date 减去 Last-Modified 值的 10%作为缓存时间。
       缓存的优先级:Cache-Control > Expires > ETag > Last-Modified
浏览器缓存机制流程图

实际场景应用缓存策略

频繁变动的资源

       频繁变动的资源首先使用 Cache-Control: no-cache 使浏览器每次都请求服务器,再配合 ETag 或者 Last-Modified 来验证资源是否有效,可以减少响应数据的大小。

代码文件

       可以用打包工具给文件名进行哈希处理,代码修改后才会生成新的文件名。然后就可以给代码文件设置一年的有效期 Cache-Control: max-age=3153600,这样就只有当 HTML 文件引入的文件名发生变化才会去下载最新的代码文件,否则就一直使用缓存。