现在的前端缓存技术方案中,主要有HTTP缓存和浏览器缓存。
HTTP缓存
HTTP缓存是服务端通过输出响应头,然后浏览器通过请求头询问服务器是否有内容更新的方式进行工作的。主要用到的HTTP头有以下几种:
Expires
Expires是HTTP1.0协议中的内容,服务端通过输出Expires头信息来告诉浏览器可以使用当前缓存的副本直到指定的过期时间为止。Expires的缺点就是使用了一个固定的时间点,客户端和服务端的时间可能会有误差。
Cache-Control
为了解决Expires的缺点,HTTP1.1协议中引入了Cache-Control,它可以和max-age同时使用指定资源可以被缓存多久。
Last-Modified / If-Modified-Since
Last-Modified是服务器告诉浏览器该资源的最后修改时间。 If-Modified-Since是客户端通过请求头发送给服务器该资源的最后修改时间。
然后服务器通过对比If-Modified-Since,若资源的最后修改过时间大于If-Modified-Since,说明该资源又被修改过,则服务端输出新的资源内容。 如果资源的最后修改时间小于或等于If-Modified-Since,则说明资源没有更新过,此时服务端响应HTTP 304状态码,告诉浏览器可以继续使用当前版本的内容。
这种通过修改时间来判断资源是否被改动过,还是会有一些误差,比如文件中的注释等毫无影响的修改都会使缓存刷新。
Etag / If-None-Match
Etag是服务端可以通过某种特定的算法生成资源文件的唯一标识,然后告诉浏览器。
浏览器再将返回的Etag值通过请求头If-None-Match传给服务端,服务端通过比对它们值是否一致决定是否返回新的资源内容,若一致则返回HTTP 304,告知浏览器可以继续使用该版本的缓存内容。
浏览器缓存
HTTP缓存本质上也属于浏览器缓存,不过目前主流的浏览器又提供了另外的接口可以存储一部分数据。它们是localStorage/sessionStorage 和 Service Worker。
localStorage
localStorage有5MB的存储空间,可以缓存网站少量常用的js资源,以减少对这些资源网络请求。微信公众号的文章页曾用过这种优化方案。
它的实现逻辑类似于以下的方式:
1
2
3
4
5
6
7
8
9
10
11
var js = localStorage.getItem('test.js');
if(js) {
eval(js);
} else {
fetch('/test.js', function(res){
if(res.ok) {
localStorage.setItem('test.js', res.body);
eval(res.body)
}
})
}
腾讯前端还开源了一个依赖localStorage做缓存的js库: https://github.com/mtjs/mt,不过它已经若干年没有进行维护了,不再建议使用。
localStorage和sessionStorage设计的初衷是为了存储应用层面的数据,而不是前端资源文件,而且它的存储空间有限。
Service Worker
Service Worker缓存是当前PWA中用到的技术。它相当于服务端和浏览器之前一个中间人的角色,通过拦截当前网站中的所有请求,我们可以通过编写代码判断哪些内容需要缓存,如果可以使用缓存就不再去向服务端发起请求。通过Service Worker可以增强网站离线缓存的体验。出于安全考虑,它只能被应用于HTTPS站点下。
使用Service Worker首先需要进行注册:
1
2
3
4
5
(function() {
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js');
}
})()
然后通过监听浏览器的fetch请求,拦截代理请求的文件,对已经缓存的资源使用本地缓存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
self.addEventListener('fetch', function(event) {
if (event.request.method === 'GET') {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) { //已缓存,使用缓存。
return response;
}
return fetch(event.request);
}
)
);
}
});
Service Worker缓存是现代浏览器缓存的一个方向,通过它可以节省大量的网络资源请求。甚至可以达到离线访问的体验。