温馨提示
本文只是下面两篇文章
HTTP缓存
Caching best practices & max-age gotchas
的阅读理解。如有错误请不吝赐教!
前段时间看了Service Worker,今天看了HTTP缓存,既然都是缓存,那么它们之间必定就有联系。
HTTP缓存的内容不多,总结成了一张思维导图,具体可以看这篇文章:
HTTP缓存
还有文章里一张很干货的图,什么时候用什么缓存策略:
关于缓存策略还可以参考这篇文章:
Caching best practices & max-age gotchas
文章主要思想如下:
- 不变的内容,用大的max-age,比如
Cache-Control: max-age=31536000
,当文件发生修改,可以通过同步更新文件名(比如,style-v1.css , style-v2.css)的方式在强制更新缓存。
这种方法不适用于博客类的网页,因为博文的url无法版本化。 - 经常改变的内容,每次请求都要向服务器验证其内容没变,
Cache-Control: no-cache
- 适当使用
max-age
会有效减轻服务器压力,但有时把max-age
用在经常改变的内容上可能会有很严重的后果。
举个例子,现在HTTP缓存了index.html
,style.css
,script.js
,在新鲜期内,缓存器丢失了style.css
,而服务器刚好又更新了这三个文件。
现在我请求这三个文件,缓存器会返回缓存的index.html
,script.js
,因为style.css
丢了,所以他要向服务器请求新的css文件。
那么,现在,我得到了旧的html,js文件,新的css文件,会出现什么情况呢?
整张页面的样式可能全乱掉了。
更可怕的是,因为三者的max-age
是一样的,那么缓存器请求了新的css文件,css文件的到期时间和html,js文件就变得不同步了!这样的连锁反应导致的后果就是,以后缓存器中的style.css
和index.html
,script.js
可能会一直保持早不兼容的状态。 - 如何解决这个问题呢?一种方法是用户刷新页面,因为刷新页面会绕过
max-age
,向服务器重新验证内容是否为最新。但是这么做会降低用户对网站的好感度。 - 第二种方法是和Service Worker合作。
终于进入正题了~~!
我们可能会想要用SW来代替HTTP缓存,比如现在写一个这样的sw.js
:
const version = '2';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
'/styles.css',
'/script.js'
]))
);
});
self.addEventListener('activate', event => {
// …delete old caches…
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
这段代码说,每次更新SW,SW在install阶段中,会缓存/styles.css
, /script.js
两个文件,之后会捕获用户的网络请求,先去找缓存里有没有对象的响应,没有的话再走正常的网络流程。
但是很尴尬的是,也许你已经从前面的“正常的网络流程”的字里行间看出来了,如果SW发送了一个网络请求,请求也会经过缓存服务器,缓存服务器很可能会返回缓存的文件。也就是说,我们还是会得到不兼容的版本。
同样的道理,SW在install阶段缓存的这两个文件,也很有可能是不兼容的旧版本。
那我们要怎么避免这种情况?一个办法是让SW每次发送的url都不一样,这样就绕过了HTTP缓存了。
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => Promise.all(
[
'/styles.css',
'/script.js'
].map(url => {
// url中添加一个随机数
return fetch(`${url}?${Math.random()}`).then(response => {
// fail on 404, 500 etc
if (!response.ok) throw Error('Not ok');
return cache.put(url, response);
})
})
))
);
});
有一些浏览器已经支持了Request接口的cache选项,可以直接这么写:
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
new Request('/styles.css', { cache: 'no-cache' }),
new Request('/script.js', { cache: 'no-cache' })
]))
);
});
我们似乎已经解决了问题,但是,用SW代替HTTP缓存可行吗?‘
看一下SW的兼容性,是不是泪流满面?因为这种方案只能解决支持SW的浏览器的问题。
更好的方法是两者结合。
在HTTP缓存中,把根页面的缓存机制设为:no-cache
,css,js文件用版本号控制变更,然后使用max-age=...
的缓存策略。每次我们更新css或者js文件,文件名也会改变。
使用下面的sw.js代码:
const version = '23';
self.addEventListener('install', event => {
event.waitUntil(
caches.open(`static-${version}`)
.then(cache => cache.addAll([
'/',
'/script-f93bca2c.js',
'/styles-a837cb1e.css',
'/cats-0e9a2ef4.jpg'
]))
);
});
每次SW更新时,都会触发一次对根页面的请求。而css,js文件只有在文件名改变时,才会打扰远程服务器。
如此,我们避免了版本不兼容的问题,也有效的节省了带宽。