浏览器缓存的全过程
- 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;
- 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;
- 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;
- 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;
- 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;
很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新
强缓存
如果资源没过期,就取缓存,如果过期了,则请求服务器,一般用于 JS、CSS、图片等资源
第一次访问页面,浏览器会根据服务器返回的 response Header 来判断是否对资源进行缓存,如果响应头中有 cache-control 或 expires 字段,代表该资源是强缓存
强缓存策略可以通过两种方式来设置,分别是 http 响应头信息中的 Expires 属性和 Cache-Control 属性
- 服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。
- Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制,它有很多不同的值
Cache-Control 可以设置的属性值,可以设置多个值
public:表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存
privite:表示只能被用户浏览器缓存,不允许任何代理服务器缓存
no-cache:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则使用协商缓存
no-store:表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源
max-age:设置缓存的最大有效期,单位为秒
Cache-Control: no-cache 和 no-store 的区别
- no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存,只不过每次都会向服务器发起请求,来验证当前缓存的有效性
- no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源
一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires
memory cache 与 disk cache 的区别
两者都属于强缓存,主要区别在于存储位置和读取速度上
- memory cache (内存缓存),顾名思义,存储在内存上的,disk cache(硬盘缓存)是存储在硬盘上的
- memory cache 要比 disk cache 快的多,从磁盘访问可能需要 5 - 20 毫秒,而内存访问只需要 100 纳秒甚至更快
- memory cache 当前 tab 页关闭后,数据将不存在(资源被释放掉了),再次打开相同的页面时,原来的 memory cache 会变成 disk cache
- disk cache 关闭 tab 页甚至关闭浏览器后,数据依然存在,下次打开仍然会是 disk cache
协商缓存
如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了
命中协商缓存的条件有两个
- Cache-Control: max-age=xxx 过期了,或者 max-age=0
- Cache-Control 的值包含 no-cache,并且不包含 no-store
使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,返回 200 状态,并且服务器返回修改后的资源
协商缓存也可以通过两种方式来设置,分别是 http 响应头信息中的 Etag 和 Last-Modified 属性
- 1、服务器通过在
响应头中添加 Last-Modified
属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头
中添加一个If-Modified-Since
的属性,属性值为上一次资源返回时的 Last-Modified
的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回304 状态
,让客户端使用本地的缓存。如果资源已经被修改了,则返回200 状态
,并返回修改后的资源 - 2、因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是
Etag 属性
。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符
,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性
,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确
为什么要有 Etag
Etag 的出现主要是为了解决一些 Last-Modified 难处理的问题
- 1、一些文件也许会周期性的更改,但是内容并不改变(仅仅改变的修改时间),这时候并不希望客户端认为这个文件被修改了而重新去请求;
- 2、某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),If-Modified-Since 能检查到的粒度是秒级的,使用 Etag 就能够保证这种需求下客户端在 1 秒内能刷新 N 次 cache
Etag 优先级高于 Last-Modified,若 Etag 与 Last-Modified 两者同时存在,服务器优先校验 Etag
协商缓存的两种状态
资源未变化,返回 304
资源已更新,返回 200
协商缓存的流程
1、第一次请求
客户端发送请求,服务器处理请求,返回文件内容和一堆 Header,包括 Etag 和 Last-Modified,状态码 200
2、第二次请求
(1)客户端发起请求,此时请求头上会带上 if-none-match 值为 Etag 和 if-modified-since 值为 last-modified
(2)服务器优先判断 Etag,若资源未变化状态码为 304,客户端继续使用本地缓存,若资源发生变化,状态码为 200,并返回最新的资源