浏览器的缓存可分为HTTP缓存和离线缓存,下面将分别介绍

HTTP缓存

先来一个例子

当我们第二次访问百度首页,在Chrome的Network面板中打开一个静态文件时会发现响应的status是:200 OK (from disk cache),不是应该返回304 Not Modified吗?如果你知道答案,那就可以忽略本文了。
百度首页缓存文件

Cache-Control

简介

w3.org 的定义是:“The Cache-Control general-header field is used to specify directives which MUST be obeyed by all caching mechanisms along the request/response chain.”

这是一个通用首部字段(就是请求报文和响应报文都能用上的字段),用来控制HTTP缓存的行为。
例如:
Cache-Control: max-age=3600, public
public意味着这个响应可以被任何人缓存
max-age则表明了该缓存有效的秒数,允许你的网站被缓存将大大减少下载时间和带宽,同时也提高浏览器的载入速度。
也可以通过设置no-cache指令来禁止缓存:
Cache-Control: no-cache

详细信息

Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。在RFC中规范了Cache-Control 的格式为:
Cache-Control: cache-directive
作为请求首部时,cache-directive 的可选值有:

作为响应首部时,cache-directive 的可选值有:

Cache-Control 允许自由组合可选值,例如:
Cache-Control: max-age=3600, must-revalidate
这意味着该资源是从原服务器上取得的,且其缓存(新鲜度)的有效时间为一小时,在后续一小时内,用户重新访问该资源时无须发送请求。

缓存校验字段

Cache-Control字段能让客户端决定是否向服务器发送请求,比如设置的缓存时间未过期,那么自然直接从本地缓存取数据即可(在chrome下表现为200 from cache),若缓存时间过期了或资源不该直接走缓存,则会发请求到服务器去。

我们现在要说的问题是,如果客户端向服务器发了请求,那么是否意味着一定要读取回该资源的整个实体内容呢?

我们试着这么想——客户端上某个资源保存的缓存时间过期了,但这时候其实服务器并没有更新过这个资源,如果这个资源数据量很大,客户端要求服务器再把这个东西重新发一遍过来,是否非常浪费带宽和时间呢?

答案是肯定的,那么是否有办法让服务器知道客户端现在存有的缓存文件,其实跟自己所有的文件是一致的,然后直接告诉客户端说这东西你直接用缓存里的就可以了,我这边没更新过呢,就不再传一次过去了

为了让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,HTTP定义了以下两种校验方法。

基于修改时间

请求首部字段

  • If-Modified-Since: 比较资源最后更新的时间是否一致

响应首部字段

  • Last-Modified: 资源的最后一次修改时间

服务器将资源传递给客户端时,会将资源最后更改的时间以Last-Modified: GMT的形式加在实体首部上一起返回给客户端。

客户端会为资源标记上该信息,下次再次请求时,会把该信息附带在请求报文中一并带给服务器去做检查,若传递的时间值与服务器上该资源最终修改时间是一致的,则说明该资源没有被修改过,直接返回304状态码即可。

基于校验码

请求首部字段

  • If-None-Match: 比较ETag是否不一致

响应首部字段

  • ETag:资源的匹配信息。

服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上ETag: 唯一标识符一起返回给客户端。

客户端会保留该 ETag 字段,并在下一次请求时将其一并带过去给服务器。服务器只需要比较客户端传来的ETag跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

关于开头的例子

现在回过头来看文章开头的问题,可能会觉得答案很容易回答出来。

百度首页的资源在刷新后实际没有发送任何请求,因为 Cache-Control 定义的缓存时间段还没到期。在Chrome中即使没发送请求,但只要从本地的缓存中取,都会在Network面板显示一条状态为200且注明from cache的伪请求,其Response内容只是上一次留下的数据。

浏览器的强制策略

当下大多数浏览器在点击刷新按钮或按F5时会强行给请求加上Cache-Control:max-age=0请求字段,所以这里提及的刷新指的是选中url地址栏并按回车键(这样不会被强行加上Cache-Control)

用serviceWorker来控制HTTP缓存

在这里简单介绍一下serviceWorker,其和webWorker一样都是一种运行在浏览器中的后台线程,只不过它的权限更大,可以拦截HTTP请求,通过编程的方式去控制HTTP缓存(HTTP缓存在JS中是全局对象caches,其类型为cacheStorage),所以只有https站点才可以使用service worker,当然localhost是一个特例。
另外serviceWorker完全遵循fetch的API。

离线缓存

离线缓存的应用场景是当APP没有网络时,也可以正常进行相关操作,在JS中是全局对象applicationCache
离线缓存有以下三个显著特点:

  • 通过mainfest文件来定义那些文件要缓存,那些文件不需要缓存
  • 总是先使用本地缓存中的资源,然后再去请求mainfest文件
  • 当本地缓存被更新后(比如调用swapCache方法手动更新),页面也不会立即使用更新过后的本地缓存文件,必须得重新打开页面才会使用新文件。

参考资料

浅谈浏览器http的缓存机制
Service Worker初体验
JavaScript中的cacheStorage使用详解
HTTP Get,Post请求详解