带你手动实践学习浏览器缓存

284 阅读6分钟

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。

缓存资源

缓存资源分为 memory cache(内存缓存)disk cache(磁盘缓存)

memory cache

不访问服务器,直接读缓存,从内存中读取缓存。此时的数据时缓存到内存中的,当kill进程后,也就是浏览器关闭以后,数据将不存在。但是这种方式只能缓存派生资源

image.png

我们经常看到的返回200显示from memory cache,其实就是取得是内存里的缓存

disk cache

不访问服务器,直接读缓存,从磁盘中读取缓存,当kill进程时,数据还是存在。这种方式也只能缓存派生资源

image.png

当我们关闭页面,在缓存还未过期的时候访问的一些访问过的派生资源时,就显示200 from disk cache其实就是磁盘缓存

push cache

Push Cache(推送缓存) 是针对HTTP/2标准下的推送资源设定的,是浏览器缓存的最后一道缓存机制,是在设置了Last-Modifed但没有设置Cache-Control或者Expires时触发,也就是只拿到最后更新时间,但没有设置过期时间,这种情况下浏览器会有一个默认的缓存策略push cache,自动设置过期时间:(Date - Last-Modified)*0.1,也就是当前时间减去最后更新时间后再乘10%。

这种策略只在会话session中存在,会话结束就被释放了。

  • 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache。

三级缓存原理 (访问缓存优先级)

  1. 先在内存中查找,如果有,直接加载。
  2. 如果内存中不存在,则在硬盘中查找,如果有直接加载。
  3. 如果硬盘中也没有,那么就进行网络请求。
  4. 请求获取的资源缓存到硬盘和内存。

后面会有例子来证明优先级是否正确


失效策略分类

按照失效策略分类,就包括两种 强缓存协商缓存

强缓存

强缓存主要包括 expires 和 cache-control。 对比来加深理解

区别expirescache-control
http版本1.01.1
时间绝对时间相对时间
优先级低于cache-control高于expires
例子expires:Mar, 06 Apr 2020 10:47:02 GMTCache-Control:max-age=3600
代表意义(例子为例)资源的失效时间,在此字段值之前则命中缓存资源的有效期是 3600 秒
缺点由于失效时间是一个绝对时间,因当服务器与客户端时间偏差较大时,就会导致缓存混乱无法兼容HTTP1.0

实践

通过实践来看看是否符合原理

用koa2的脚手架创建一个项目

koa2 -e koa2-learn (koa2-learn代表项目名称)

创建完成之后在public文件夹里新建一个html以及在images放一个图片

<!-- public/index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>前端缓存</title>
    <style>
      .web-cache img {
        display: block;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="web-cache"><img src="./images/unique.jpg" /></div>
  </body>
</html>

在app.js文件中设置cache-control

注意:新添加的代码块必须得在koa-static引用之前才会生效

// 新添加 --start
app.use(async (ctx, next) => {
  // 设置响应头Cache-Control 设置资源有效期为300秒
  ctx.set({
    'Cache-Control': 'max-age=300',
  })
  await next()
})
/// 新添加 --end

app.use(require('koa-static')(__dirname + '/public'))

将项目跑起来看看效果(记得不要勾选disable cache,勾选了就不会有缓存)

image.png

可以发现设置的cache-Control生效了,第一次请求浏览器会把图片存进了磁盘(disk cache)和内存(memory cache)中。


取内存资源

紧接着我们来刷新一下,根据三级缓存原理,我们会先在内存中找资源 image.png 发现图片是直接从内存中取的,符合


取磁盘资源

memory cache在关闭进程的时候会释放掉其中的资源,现在我们关闭浏览器,重新打开看看当内存没有该图片的时,是否会取磁盘内存里的图片

image.png 发现图片取了磁盘内存,符合


验证cache-control

我们设置的缓存有效期是300s,当过了300s后我们重新刷新一下,来验证一下缓存是否失效

image.png 可以发现缓存失效了,跟我们第一次请求的时候一模一样

协商缓存

协商缓存就是通过服务器来判断缓存是否可用,服务器则根据header的信息( Last-Modify/If-Modify-Since ETag/If-None-Match)来判断是否命中协商缓存,如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-ModifyLast-modify 是一个时间标识该资源的最后修改时间。

当浏览器再次请求该资源时,request 的请求头中会包含If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。

如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。

缺点:短时间内资源发生了改变,Last-Modified 并不会发生变化。

ETag/If-None-Match

Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。

web服务器收到请求后发现有头If-None-Match则与ETahe进行对比,相同则返回304不同则返回200

常见问题

  1. Etag与Last-Modified有什么区别?

Etag更倾向于标识资源是否有变更,而Last-Modified更倾向于含有时间状态的数据

  1. 如果Etag和Last-Modified同时存在,服务器会先检测哪一个?

服务器会先检测Etag再去检测Last-Modified,因为Etag发生改变的话那么资源的内容一定发生了变化,而Last-Modified发生了变化资源内容不一定发生改变

Etag实践

安装下面两个依赖

yarn add koa-conditional-get , yarn add koa-etag

const conditional = require('koa-conditional-get')
const etag = require('koa-etag')

app.use(conditional())
app.use(etag())

app.use(require('koa-static')(__dirname + '/public'))

看一下第一次请求的时候效果

image.png


发现已经有etag的值了,现在我们来刷新看看

image.png 当我们再次请求的时候Request Headers上的If-None-Match已经带上了Etag的值了


接下来我们将一个新的图片跟现有图片重命名覆盖掉,刷新请求

image.png 发现协商缓存失效了,Request Headers上的If-None-Match还是上一张图片的Etag值,与Response Headers返回来的Etag不相等所以重新请求新资源返回200。