浏览器缓存是性能优化中最直接,高效的优化方式,它可以显著减少因为网络传输而带来的损耗。
- 对于数据请求来说,大致可以分为 请求 -> 处理 -> 响应这三个步骤,而浏览器缓存则主要在第一步和第三步做手脚,即请求发出时寻找合适的缓存,拿到新的响应数据时做新的缓存。
- 缓存带给我们最直观的感受就是,每次加载页面,第二次之后加载总是比第一次加载的更快,这就是缓存的功劳,下面讲从缓存位置和缓存策略两个方面介绍浏览器相关缓存。
缓存位置
- 浏览器的缓存位置大概可以分为四种:
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache(HTTP 2)
- 他们有各自的优先级,当发出请求时,浏览器会依次去寻找缓存,如果都没有命中,才会发出请求。
Service Worker
- Service Worker是浏览器背后的独立线程,可以用来实现缓存功能,但是需要注意,如果要使用Service Worker,传输协议必须是HTTPS。
- Service Worker实现缓存大致三个步骤。
- 注册Service Worker。
- 监听install事件,并对需要的文件进行缓存。
- 拦截HTTPS请求,并根据请求内容去命中缓存,如果命中,则直接使用缓存,否则请求数据。
- 下面是一串实例代码(源代码来自前端面试之道)
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register('sw.js')
.then(function(registration) {
console.log('service worker 注册成功')
})
.catch(function(err) {
console.log('servcie worker 注册失败')
})
}
// sw.js
// 监听install事件,缓存文件
self.addEventListener('install', e => {
e.waitUntil(
caches.open('my-cache').then(function(cache) {
return cache.addAll(['./index.html', './index.js'])
})
)
})
// 拦截请求,并根据请求去命中响应数据
self.addEventListener('fetch', e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response
}
console.log('fetch source')
})
)
})
Memory Cache -> 内存缓存
- 由于是内存缓存,所以速度会比后续马上讲到的磁盘缓存速度快很多,但是内存空间非常有限,所以一般内存缓存的内容都比较小,且生命周期短,会随着进程的释放而释放(关闭页面)。
- 内存缓存一般是css,js,svg,小图片文件等,打开浏览器的开发者工具,重新刷新网络页面,可以发现很多请求右侧都有
memory cache
的提示,说明这些请求就是命中内存缓存中的内容而直接返回的内容。
Disk Cache
- 磁盘缓存虽然速度没有内存缓存快,但是容量大,基本什么内容都可以存在磁盘中,并且可以存储内容的生命周期长。
- 磁盘缓存会根据HTTP Header中国呢的字段判断哪些内容需要缓存,哪些资源可以直接使用,哪些资源已经过期需要重新请求。需要注意的是,相同地址的资源一旦被磁盘缓存下来,就不会再次去请求数据,比如我直接更改博客图片链接所指向的图片内容(链接不变),重新刷新后会发现图片依然是原来的图片。
Push Cache(Http2)
- push cache是HTTP/2的内容,当上面三个缓存都没有命中时,才会被使用。并且缓存的时间非常短暂,只在session中存在,一旦session结束就会被释放。由于接触的不多,这里不细讲。
缓存策略
缓存策略分为两种:强缓存和协商缓存。并且它们的缓存策略都是通过HTTP Header来实现的。需要注意的是,这两种缓存并不是互斥的,即强缓存和协商缓存在一次请求中是同时存在的,下面会细说。
强缓存
- 强缓存通过设置两种HTTP Header来实现:
Expires
和Cache-Control
。强缓存表示的是在缓存期间内不需要请求,响应码为200.
Expires
Expires: Wed, 22 Oct 2018 08:41:00 GMT
- Expires是HTTP/1的响应头,它表示的是请求的资源会在上述的时间后过期。如果资源会再次过期,并且
Expires
是基于本地时间进行判断的,如果人为更改了本地时间,可能会造成缓存失效。
Cache-Control
cache-control: public, max-age=31536000, s-maxage=31536000, immutable
- Cache-Control是HTTP/1.1的请求/响应头,并且它的优先级高于Expires,该资源表示资源是可以被客户端,代理服务器缓存的,且缓存时间为一年。
- 上述可以看到,Cache-Control可以使用多种指令,从而达到多个目的,下面是一些常见指令的作用。
协商缓存
协商缓存一般发生在强缓存不能进行的时候,当浏览器需要命中缓存却发现缓存已经过期时,它不会立即删除缓存,而是抱着试一试的态度,带着缓存的唯一标识信息去询问后端资源有没有更改,如果没有更改,就直接返回304(Not Modified),并更新本地缓存有效期。
和强缓存类似,协商缓存也需要借助HTTP Header字段的帮助,它们分别是
Last-Modified
和E-Tag
Last-Modified 和 If-Modified-Since -> HTTP/1
- Last-Modified 表示本地文件最后修改日期,If-Modified-Since会把Last-Modified的值发送给服务器,询问服务器在该时间段后,资源是否发生了更改,如果有更新,则会正常请求资源(200),并更新本地缓存,如果没有,则直接返回缓存资源(304)。
ETag 和 If-None-Match
ETag
类似于文件id,If-None-Match会把ETag的值发送给服务器,并询问对应资源是否有更改,如果没有变动就直接返回本地缓存,如果有变动则返回新的资源,并更新本地缓存。需要注意的是,ETag的优先级比Last-Modified高。