持续创作,加速生长!这是我参加「日新计划 10 月更文挑战」的第30天,点击查看活动概况

前言

缓存是优化手法中不可或缺的一部分,面试时也常常问到,它能够加速页面的呼应速度、减轻服务器的压力。很多人对这方面的了解都是看过相关文章,可是没有实际操作过,今天我带咱们手把手走一遍这个流程,让浏览器缓存知识变成真正属于自己的知识。

建立 express 服务端

首要咱们先用 express 结构建立一个简单的服务端。

三部曲运行服务器

三部曲第一步:创建一个空文件夹,进入终端生成一个 package.json 文件。

npm init -y

三部曲第二步:装置 express 依靠。

npm install express --save

三部曲第三步:创建一个 index.js 文件。

const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
  res.send('Hello World!')
})
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

浏览器翻开 localhost:3000 看看刚起的服务:

「代码实操」 从根儿上了解浏览器缓存

不出意外咱们看到的是这个画面。紧接着就能够进行下一步了。

打印恳求资源途径

接下来咱们要做一件事 —— 将每次向服务端恳求资源的途径都打印出来 。这样便利咱们后续对缓存未射中,调查哪些资源是需求从头向服务端恳求的。

app.all("*", function (req, res, next) {
    console.log(req.originalUrl);
    next();
});
app.use('/public', express.static(__dirname + '/public', {
    ...
}))

这儿要留意顺序之分,先 app.allapp.use

静态文件映射

假如你想把所有的静态文件途径都前缀”/static”, 你能够使用“挂载”功用。 假如req.url不包含这个前缀, 挂载过的中间件不会执行。 当function被执行的时分,这个参数不会被传递。 这个只会影响这个函数,后边的中间件里得到的req.url里将会包含”/static”

// GET /static/javascripts/jquery.js
// GET /static/style.css
// GET /static/favicon.ico
app.use(express.static('/public'));
declare namespace serveStatic {
    var mime: typeof m;
    interface ServeStaticOptions<R extends http.ServerResponse = http.ServerResponse> {
        cacheControl?: boolean | undefined;
        etag?: boolean | undefined;
        maxAge?: number | string | undefined;
        lastModified?: boolean | undefined;
    }
    ...
}

首要咱们 禁用所有缓存 。具体的,依据 ts 文件咱们能够知道,etagcacheControl 以及 lastModified 都能够接受一个 Boolean 值,那么咱们能够给它们三个设置 false,表明禁用,而 maxAge 咱们能够设置一个 0,表明禁用。

app.use(express.static('/static', {
    etag: false,
    cacheControl: false,
    lastModified: false,
    maxAge: 0
}))

功用测验

接下来拜访 localhost:3000/public/index.html 进行测验:

「代码实操」 从根儿上了解浏览器缓存

终端资源恳求途径:

「代码实操」 从根儿上了解浏览器缓存

ps:小伙伴们测验缓存的时分重点关注 status 和 size 这两栏就好了,后边不再赘述。

不论改写多少次,状况码都是是 200,Size 栏那边也没有任何表明 test.js 是从缓存中读取的信息,而且每次改写时,终端中都会打印这两个文件 —— 这说明每次都是从服务端直接获取的。

强缓存

首要咱们测验强缓存 —— 在设定的过期时刻内,浏览器都不会再向服务器恳求资源,而是直接从浏览器缓存中加载资源。

Expires

Expires 是 HTTP 1.0 时期提出的一个 表明资源过期的肯定时刻 的头部。

测验

接下来咱们测验给呼应的资源加上 Expires 头部:

app.use('/public', express.static(__dirname + '/public', {
    ...
    setHeaders: function (res, path, stat) {
        res.append('Expires', new Date(new Date().getTime() + 15 * 1000));
    }
}))

这儿咱们将 资源过期时刻 设置为 15 秒,即 15 秒后,强缓存失效,需求向服务端从头恳求资源。

重启服务,然后再次拜访 localhost:3000/public/index.html,咱们来看看浏览器中恳求的结果以及终端里当时恳求的资源途径:

  1. 初次拜访页面时加载了 index.html 文件以及 test.js 文件:

    「代码实操」 从根儿上了解浏览器缓存
    「代码实操」 从根儿上了解浏览器缓存

  2. 15 秒内,改写页面从头加载 test.js 文件,此刻强缓存仍有用,不必向服务端建议资源恳求,而是直接从浏览器缓存中获取资源:

    「代码实操」 从根儿上了解浏览器缓存

  3. 超出 15 秒后,从头加载 test.js 文件,发现强缓存失效,需求从头向服务端建议资源恳求:

    「代码实操」 从根儿上了解浏览器缓存
    「代码实操」 从根儿上了解浏览器缓存

具体过程:

  1. 浏览器初次加载资源时向服务端宣布资源恳求,随后服务端呼应资源,一起设置了 Expires 呼应头,表明资源过期的肯定时刻 ,浏览器接纳资源的一起会将这个 Response Header 缓存下来。
  2. 15 秒内再次加载该资源时,经过比对此前缓存的 Response HeaderExpires 的时刻,断定射中了强缓存,会直接从浏览器缓存中加载资源,状况码 200,一起 Size 栏显现 momory cache
  3. 15 秒后加载该资源时,相同经过比对,发现超出过期时刻,则断定强缓存失效,会从头向服务端发送资源恳求,浏览器接纳呼应资源的一起缓存该 Response Header

缺陷

咱们从上面能够知道,Expires 是服务端往呼应头中设置的 肯定时刻,假如说服务器和浏览器的时刻不一致的话,是很不稳定的 —— 客户端修正本地时刻时或许时区不一致时,都或许产生问题。

Cache-Control

HTTP 1.1 时期增加 Cache-Control 呼应头来改善这个问题。

测验

Cache-Control 的默认值为 public, max-age=0 。因而咱们要修正 maxAge 特点。

cacheControl?: boolean | undefined;
maxAge?: number | string | undefined;

经过 ts 文件咱们能够看出 cacheControl 接纳的是 Boolean 值,咱们要将它设置为 true 。一起,将 maxAge 特点设置为 15 秒(留意这儿的 maxAge 特点单位是毫秒,呼应头的 max-age 单位是秒,因而要留意单位换算)。

app.use('/public', express.static(__dirname + '/public', {
    etag: false,
    cacheControl: true,
    lastModified: false,
    maxAge: 15 * 1000, // 留意这儿的 maxAge 单位是毫秒,呼应头的 max-age 单位是秒
    // setHeaders: function (res, path, stat) {
    //     res.append('Expires', new Date(new Date().getTime() + 15 * 1000));
    // }
}))

重启服务,然后拜访 localhost:3000/public/index.html:

  1. 初次拜访页面时加载了 index.html 文件以及 test.js 文件:

    「代码实操」 从根儿上了解浏览器缓存
    「代码实操」 从根儿上了解浏览器缓存

  2. 15 秒内,改写页面从头加载 test.js 文件,此刻强缓存仍有用,不必向服务端建议资源恳求,而是直接从浏览器缓存中获取资源:

    「代码实操」 从根儿上了解浏览器缓存

  3. 超出 15 秒后,从头加载 test.js 文件,发现强缓存失效,需求从头向服务端建议资源恳求:

    「代码实操」 从根儿上了解浏览器缓存
    「代码实操」 从根儿上了解浏览器缓存

喜爱看 动图 的小伙伴看这儿:

「代码实操」 从根儿上了解浏览器缓存

具体过程:

  1. 15 秒后,相同的操作,可是发现超出 当时时刻,断定强缓存过期,于是从头向服务端发送资源恳求,浏览器接纳资源的一起将缓存该呼应头。

  2. 浏览器初次加载资源时向服务端宣布资源恳求,随后服务端呼应资源,一起设置了 Cache-Control 呼应头,表明资源过期的相对时刻 ,浏览器接纳资源的一起会将这个 Response Header 缓存下来。

  3. 15 秒内再次加载该资源时,经过比对此前缓存的 Response Header 中的资源过期时刻 Cache-Control + 资源恳求时刻 Date,发现 未超出当时时刻 ,则断定射中了强缓存,会直接从浏览器缓存中加载资源,状况码 200,一起 Size 栏显现 momory cache

  4. 15 秒后加载该资源时,相同经过比对,发现 超出当时时刻 ,则断定强缓存失效,会从头向服务端发送资源恳求,浏览器接纳呼应资源的一起缓存该 Response Header

Cache-Control 和 Expires 的优先级

假如 Cache-ControlExpires 一起存在会以谁的时刻为准呢?咱们来测验一下:

app.use('/public', express.static(__dirname + '/public', {
    etag: false,
    cacheControl: true,
    lastModified: false,
    maxAge: 10 * 1000,
    setHeaders: function (res, path, stat) {
        res.append('Expires', new Date(new Date().getTime() + 15 * 1000));
    }
}))

重启服务,然后拜访 localhost:3000/public/index.html。

「代码实操」 从根儿上了解浏览器缓存

能够发现强缓存在 10 秒后就失效了。

因而当 Cache-ControlExpires 一起存在时,会以 Cache-Control 的时刻为准。

小结

经过上面的了解,咱们应当知道:

  1. 强缓存 Expires ,表明的是一个 服务端为准的肯定时刻
  2. 强缓存 Cache-Control ,表明的是一个过期秒数,过期时刻是以 客户端为准的相对时刻
  3. 两者均存在则以 Cache-Control 的时刻为准。
  4. 加载资源时,假如射中强缓存(在有用期内),则呼应状况码 200,且有 memory cache 字样,表明该资源是从浏览器缓存中加载的。若缓存失效后会从头向服务端建议资源恳求,服务器呼应资源,浏览器接纳呼应的一起将 Response Header 缓存下来。
  5. 强缓存是由服务器设置的,可是是由浏览器来判断缓存是否还有用。

洽谈缓存

假如没有射中强缓存,就会判断是否射中洽谈缓存,此刻浏览器会向服务端发送恳求,确认资源是否被修正(是否有用),假如资源未修正则告知浏览器仍可用缓存的资源,不然服务端呼应时会将资源一并呼应。

Last-Modified 和 If-Modified-Since

洽谈缓存的完成都是成双成对的,咱们先介绍 Last-ModifiedIf-Modified-SinceLast-Modified 表明资源的最终修正时刻,相似这样:

「代码实操」 从根儿上了解浏览器缓存

测验

lastModified?: boolean | undefined;

从 ts 文件中咱们能够知道 lastModified 特点接纳一个 boolean 值,true 表明敞开,咱们来测验一下:

app.use('/public', express.static(__dirname + '/public', {
    etag: false,
    cacheControl: true,
    lastModified: true,
    maxAge: 10 * 1000,
}))

咱们看看效果:

「代码实操」 从根儿上了解浏览器缓存

具体过程:

  1. 浏览器初次加载资源时向服务端宣布资源恳求,随后服务端呼应资源,一起设置了 ExpiresLast-Modified 呼应头,浏览器接纳资源的一起会将这个 Response Header 缓存下来。
  2. 10 秒内再次加载该资源时,经过比对此前缓存的 Response HeaderExpires 的时刻,断定射中了强缓存,会直接从浏览器缓存中加载资源,状况码 200,一起 Size 栏显现 momory cache
  3. 10 秒后加载该资源时,相同经过比对,发现超出过期时刻,则断定强缓存失效,会从头向服务端发送资源恳求,验证是否射中洽谈缓存。此次恳求会将缓存的 Response Header 中的 Last-Modified 值作为这次恳求头 If-Modified-Since 的值,服务端接纳恳求后,将恳求头中 If-Modified-Since 与所恳求资源的最终修正时刻比对,依据是否修正来断定是否过期 ,假如资源未修正则回来状况码 304 Not Modified,表明射中洽谈缓存;不然从头回来资源和 新的最终修正时刻

「代码实操」 从根儿上了解浏览器缓存

缺陷

乍一看好像这样挺好的,没啥缺点,真的是这样吗?

为了便利测验洽谈缓存的缺陷,咱们直接封闭 cacheControl 强缓存,使用 Expires ,然后将强缓存设置为当时时刻之前,如此一来强缓存会一向过期:

app.use('/public', express.static(__dirname + '/public', {
    etag: false,
    cacheControl: false,
    lastModified: true,
    maxAge: 0,
    setHeaders: function (res, path, stat) {
        res.append('Expires', new Date(new Date().getTime() - 15 * 1000));
    }
}))

咱们刚刚说到,lastModified 表明的是资源的最终修正时刻,那么这个最终修正时刻必定精确更新吗?

咱们先右键特点看看 test.js 文件的最终修正时刻:

「代码实操」 从根儿上了解浏览器缓存

时刻是 22:21:06。再看看呼应头中 Last-Modified 的值:

「代码实操」 从根儿上了解浏览器缓存

这儿的时刻是 GMT 时刻 ,咱们是在东八区,因而需求加上 8 小时,即 14:21:06 + 8:00 也便是 22:21:06。

这么看来最终修正时刻是没问题的,接下来咱们来测验。

这是此刻 test.js 文件中的内容:

// test.js
console.log(123);

接下来咱们进行修正,留意不要保存:

// test.js
console.log(1234);

然后再修正回去,保存。

接下来咱们拜访 localhost:3000/public/index.html:

「代码实操」 从根儿上了解浏览器缓存

咱们惊讶的发现 test.js文件内容没有修正 ,可是 Last-Modified 的值变了,而且状况码回来 200,咱们再次改写后状况码又变成 304,也便是射中了洽谈缓存。

有的小伙伴说:那你的确便是改了文件啊,“最终修正时刻”变了不应该吗?

没错,逻辑没缺点,可是它现已不符合咱们的需求了 —— 缓存资源 。咱们想想为什么要缓存资源?因为它 内容没变化 ,所以咱们不应该让服务器回来咱们恳求的资源的。test.js 的文件内容并没有变化,从需求上来看应该射中洽谈缓存的,但是没有 。这便是 Last-Modified 这个形式的弊端了 —— 不行精确。

除此之外,假如在极短时刻内修正文件一起进行恳求,那么“最终修正时刻”也有或许没变。

ETag 和 If-None-Match

结论为先,ETag 会依据 文件的内容 + 文件修正时刻 生成一个 hash 值。

这个和 Last-Modified 的断定逻辑差不多,不赘述了:

「代码实操」 从根儿上了解浏览器缓存

需求留意的是,由于 ETag 是依据 文件的内容 + 文件修正时刻 生成的 hash 值,因而 Last-Modified 的缺陷它也有 —— 文件内容不变但 ETag 的值变了,缓存失效。

可是最少它能解决 Last-Modified 的另一个缺陷。

小结

  1. 强缓存未射中时,会进行洽谈缓存断定,假如洽谈缓存射中,则状况码为 304 Not Modified。洽谈缓存未射中,则回来资源,一起更新对应的呼应头。
  2. Last-Modified 是依据恳求头中 If-Modified-Since 的值与资源的最终修正时刻来断定是否射中洽谈缓存的,容易误判。
  3. ETag 是依据恳求头中 If-None-Match 的值与资源生成的 hash 值来断定是否射中洽谈缓存的。

流程图

「代码实操」 从根儿上了解浏览器缓存

结束语

本文就到此结束了,信任这么一番操作后,咱们对浏览器缓存的了解更加深刻了。

假如小伙伴们有其他想法,欢迎留言,让咱们一起学习进步。

假如文中有不对的地方,或是咱们有不同的见解,欢迎指出。

假如咱们觉得所有收成,欢迎一键三连。