# 能不能说一说浏览器缓存

缓存是性能优化中非常重要的一环,浏览器的缓存机制对开发也是非常重要的知识点。接下来以三个部分来把浏览器的缓存机制说清楚:

  • 缓存分类 和 缓存位置
  • 强缓存
  • 协商缓存

# 缓存分类

web缓存分为很多种,比如数据库缓存、代理服务器缓存、还有我们熟悉的CDN缓存

浏览器向源服务器发起请求的流程如下:

浏览器缓存是将文件保存在客户端,在同一个会话过程中会检查缓存的副本是否足够新,在后退网页时,访问过的资源可以从浏览器缓存中拿出使用。通过减少服务器处理请求的数量,提高性能。

# 缓存位置

Memory Cache:内存缓存 (当前浏览器窗口关闭了,也就是失效了)

Disk Cache:硬盘缓存

说到浏览器缓存的缓存位置,并不是把缓存存在浏览器里,而是浏览器自己建立一些缓存机制,把缓存信息存储到内存条里(暂时缓存),比如打开某个页面,执行某个js脚本的时候,会在内存条里,开辟一些栈空间,用于执行代码,当页面关闭后,有的内存就会被释放,有的内存就会造成泄漏问题,不会被释放。

然而,浏览器还会把一些内存信息存到硬盘里,存到硬盘里的信息,并不会因为断电而丢失掉。

# 缓存检查

# 浏览器缓存一般在什么时刻被查找并获取呢?

一般在我们打开浏览器输入一个网页的时候,浏览器并不会立即向服务器发送请求,而是先检查本地是否有缓存,如果有缓存,则直接从缓存中读取;没有缓存时,才向服务器发送请求,拿到最新信息。

# 检查缓存的一些场景

  • 打开网页:查找disk cache中是否有匹配,如果有则使用,如没有则发送网络请求
  • 普通刷新(F5):因Tab没关闭,因为memory cache是可用的,会被优先使用,其次才是disk cache
  • 强制刷新(Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有Cache-control: no-cache,服务器直接返回200和最新内容

# 强缓存

浏览器中的缓存作用分为两种情况,一种是需要发送 HTTP 请求,一种是不需要发送请求

首先是检查强缓存,这个阶段不需要发送 HTTP 请求。

# 检查强缓存机制的流程

打开网页,第一次校验强缓存流程

强缓存的执行流程,第一次发送请求的时候,就如图上的5个步骤,不再详细描述

上图是打开网页,第一次校验强缓存的流程,那么第二次呢?

第二次再请求该页面的时候,会查浏览器缓存的结果和标识,如果有强缓存的标识,那么直接从浏览器缓存中读取

上面便是,完整的强缓存机制。

如何来检查呢?通过相应的字段来进行,但是说起这个字段就有点门道了。

HTTP/1. 0HTTP/1. 1 当中,这个字段是不一样的。

HTTP/1. 0 时期,使用的是Expires,而 HTTP/1. 1 使用的是Cache-Control

让我们首先来看看Expires

# Expires

Expires过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间 之前 可以直接从浏览器的缓存里面获取数据,无需再次请求。比如下面这样:

Expires: Wed, 22 Nov 2019 08: 41: 00 GMT

表示资源在2019年11月22号8点41分过期,过期了就得向服务端发请求。

这个方式看上去没什么问题,合情合理,但其实潜藏了一个坑,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是 不准确 的。因此这种方式很快在后来的HTTP1. 1版本中被抛弃了。

# Cache-Control

在HTTP1. 1中,采用了一个非常关键的字段: Cache-Control 。这个字段也是存在于服务端返回的响应头中。

它和 Expires 本质的不同在于它并没有采用具体的 过期时间点 这个方式,而是采用 过期时长 来控制缓存,对应的字段是max-age。比如这个例子:

代表这个响应返回后在 3600 秒, 也就是一个小时之内可以直接使用缓存
Cache - Control: max - age = 3600

如果你觉得它只有max-age一个属性的话,那就大错特错了。

它其实可以组合非常多的指令,完成更多场景的缓存判断, 将一些关键的属性列举如下:


max-age(单位为s)指定设置缓存最大的有效时间,定义的时时间长短 。当浏览器向服务器发送请求后,在max-age这段时间里浏览器就不会在向服务器发送请求了。优先于 Expires

public 指定响应会被缓存,并且在多用户间共享客户端代理服务器 都可以缓存。因为一个请求可能要经过不同的 代理服务器 最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。



private:这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。

no-cache:跳过当前的 强缓存 ,强制发送 HTTP 请求,即直接进入 协商缓存 的阶段。

no-store:禁止一切缓存(这才是响应不被缓存的意思)。

s-maxage(只用于共享缓存,如CDN缓存):这和 max-age 长得比较像,但是区别在于s-maxage是针对 代理服务器 的缓存时间。比如,当x-maxage=60时,即使更新了CDN的内容,浏览器也不会请求。

值得注意的是: 当ExpiresCache-Control同时存在的时候,Cache-Control会优先考虑。

# 强缓存的特点

服务器设定了一些资源(静态资源/css/js/图片...)的“强缓存机制”,在浏览器缓存的有效期内,我们除了强制清缓存刷新外,正常加载页面,都是从强缓存中获取数据,而不是从服务器获取。

# 强缓存的优势与弊端

强缓存的优势:

减少对服务器的请求次数,加载资源更快,页面渲染速度更快

强缓存的弊端:

当我们的资源在服务器更新了,但是本地还是有缓存的,这样导致客户端无法及时获取最新的资源

避免强缓存带来的缺陷:

  • 首页HTML不做缓存,每一次发布资源的时候,内容有更新(资源文件名字不一样,例如webpack 在名字上设置 HASH),这样页面请求的资源文件也就变了,客户端没有新文件的缓存,就从服务器获取了
  • 哪怕文件名不变,只要在请求资源文件的后面加一个时间戳也是可以的,时间戳不同,从服务器重新获取资源
  • 不做强缓存的设置,基于协商缓存实现(真实项目中,往往 两者同时 设置

当然,还存在一种情况,当资源缓存时间超时了,也就是 强缓存失效 了,接下来怎么办?没错,这样就进入到第二级屏障——协商缓存了。

# 协商缓存

强缓存失效之后,浏览器在请求头中携带相应的 缓存tag 来向服务器 发请求 ,由服务器根据这个tag,来决定是否使用缓存。

HTTP/1. 0HTTP/1. 1 当中,这个字段是不一样的。

HTTP/1. 0 时期,使用的是Last-Modifed,而 HTTP/1. 1 使用的是Etag

# 检查协商缓存机制的流程

检查协商缓存机制的流程

协商缓存流程 Last-Modified和If-Modified-Since

  • 第一次访问资源,服务器返回资源的同时,响应头中设置Last-Modified(服务器上的最后修改时间), 浏览器接收后,缓存文件和响应头;
  • 下一次请求这个资源,浏览器检测到有Last-Modified,于是添加If-Modified-Since请求头,值就是Last-Modified中的值;
  • 服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200;

但是Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源;

协商缓存流程 ETag 和 If-None-Match

  • Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成;

  • 下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。

    • ETag匹配失败:服务器返回常规 GET 200的形式,将 最新的资源发给客户端
    • ETag匹配一致:返回304,直接读取本地缓存

# Last-Modified

即是 服务器文件的最后修改时间 。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。

当浏览器接收到后,如果再次请求,会在请求头中携带 If-Modified-Since 字段,询问 Last-Modifed 时间之后资源是否被修改过

  • 如果没有被修改过,则返回状态码304,——使用协商缓存
  • 如果修改过,则再去服务器请求资源,返回码和首次请求的相同为200,资源为服务器最新资源

# ETag

Etag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过 响应头 把这个值给浏览器。

# 两者对比

  1. 精确度 上, ETag 优于 Last-Modified 。ETag是按照内容给资源上标识,因为能准确感知资源的变化。而Last-Modified就不一样了,它在一些情况是不能准确感知到资源变化的

    • 编辑 了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
    • Last-Modified 能够感知的单位时间是秒,如果文件在1秒内改变了很多次,那么这时候的Last-Modified并没有体现出修改了。
  2. 性能上Last-Modified 优于 ETag ,也很简单理解, Last-Modified 仅仅是记录一个时间点,而 ETag 需要根据文件的具体内容生成哈希值。


另外,如果两种方式都支持的话,服务器会优先考虑 **ETag** 的。

强缓存: 本地有缓存则不向服务器发送请求

协商缓存: 哪怕本地有缓存也需要向服务器发请求校验资源文件是否更改

  • 有更改,从服务器拿最新的资源 (拿回来后缓存到本地)
  • 没有更改,服务器啥都不返回,客户端再从本地拿缓存信息

# 缓存请求的基本流程

缓存请求的基本流程

浏览器在加载资源时,根据请求头中的的 ExpiresCach-control 判断是否命中强缓存:

  • 如果缓存存在(强缓存),则直接从浏览器中读取缓存,返回状态码304不会发送请求到服务器

  • 如果没有命中强缓存,再向 服务器发送个 请求,通过 Last-ModifiedETag 判断资源是否 更新被修改 过,如果没有被更新,则根据 协商缓存304 返回浏览器中的内容。

  • 如果前面都没命中,则直接从服务器请求资源回来,返回200状态码

# 图解浏览器整个缓存流程

图解浏览器整个缓存流程

综上所述,从浏览器的第一次打开网页,请求资源,检查本地的浏览器的缓存结果和缓存标识,再到强缓存失效后的协商缓存校验。我们一一做了详细的描述,最后附上从用户请求到整个浏览器缓存的过程的流程图。

借鉴:
浏览器缓存
神三元-浏览器缓存