Skip to content

浏览器缓存机制

前言

昨天面网易的时候( 因为基本都是跟着我的项目来深挖,就不出面经了 ),他们有问到浏览器的缓存问题,他们希望我分别从 http 缓存和本地缓存两个方面来回答,本地缓存我当然是答上来了,但是 http 的缓存只依稀记得计网学的一些皮毛,所以今天就重新系统学习了一下这两个,然后出了这一篇文章。

缓存有什么作用

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

http 缓存

对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

接下来的内容中我们将通过 DNS 缓存、缓存位置、缓存策略以及实际场景应用缓存策略来探讨浏览器的 http 缓存机制。

DNS 缓存

通常我们输入一个网址,它包含了域名和端口可以指定唯一的 IP 地址,然后建立连接进行通信,而域名查找 IP 地址的过程就是 dns 解析。

css
www.dnscache.com (域名) - DNS解析 -> 11.222.33.444 (IP地址)

这个过程会对网络请求带来一定的损耗,所以浏览器在第一次获取到 IP 地址后,会将其缓存起来。下次相同域名再次发起请求时,浏览器会先查找本地缓存,如果缓存有效,则会直接返回该 IP 地址,否则会继续开始寻址之旅。

  • 首先搜索浏览器自身的 DNS 缓存,如果存在,则域名解析到此完成。
  • 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的 hosts 文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
  • 如果本地 hosts 文件不存在映射关系,则查找本地 DNS 服务器(ISP 服务器,或者自己手动设置的 DNS 服务器),如果存在,域名到此解析完成。
  • 如果本地 DNS 服务器还没找到的话,它就会向根服务器发出请求,进行迭代查询/递归查询。

迭代查询和递归查询

markdown
// 名词解析

- 域名:`www.google.com`
- 客户端配置的本地名称服务器:`dns.google.com`
- 域名根目录名称服务器`a.rootserver.net`

- 主要角色

  - DNS 客户 -> `DNS 客户`
  - 本地名称服务器 -> `Local DNS`
  - 根名称服务器 ->`.`
  - 一级(顶级)名称服务器 -> `com`
  - 二级名称服务器 -> `google.com`
  - 权威名称服务器 -> `www.google.com`
迭代查询
  1. DNS 客户端向本机配置的本地名称服务器发出 DNS 域名查询请求。
  2. 本地名称服务器收到请求后,先查询本地的缓存,如果有该域名的记录项,则本地名称服务器就直接把查询的结果返回给客户端;如果本地缓存中没有该域名的记录,则向 DNS 客户端返回一条 DNS 应答报文,报文中会给出一些参考信息,如本地名称服务器上的根名称服务器地址等。
  3. DNS 客户端在收到本地名称服务器的应答报文后,会根据其中的根名称服务器地址信息,向对应的根名称服务器再次发出与前面一样的 DNS 查询请求报文。
  4. 根名称服务器在收到 DNS 查询请求报文后,通过查询自己的 DNS 数据库得到请求 DNS 域名中顶级域名所对应的顶级名称服务器信息,然后以一条 DNS 应答报文返回给 DNS 客户端。
  5. DNS 客户端根据来自根名称服务器应答报文中的对应顶级名称服务器地址信息,向该顶级名称服务器发出与前面一样的 DNS 查询请求报文。
  6. 顶级名称服务器在收到 DNS 查询请求后,先查询自己的缓存,如果有所请求的 DNS 域名的记录项,则直接把对应的记录项返回给 DNS 客户端,否则通过查询后把对应域名中二级域名所对应的二级名称服务器地址信息以一条 DNS 应答报文返回给 DNS 客户端。
  7. DNS 客户端继续按照步骤 5 与步骤 6 的方法分别向三级、四级名称服务器查询,直到查到最终的权威名称服务器返回到最终的记录。
markdown
假设客户端想要访问自己并不识别的站点www.google.com,那么 DNS 客服端的查询路径如下:

1. DNS 客户端向所配置的本地名称服务器 dns.google.com 发出解析 www.google.com 域名的 DNS 请求报文。
2. 本地名称服务器收到 客户端的 DNS 查询请求报文后,先查询本地缓存。假设没有查到该域名对应记录,则本地名称服务器把所配置的根名称服务器 a.rootserver.net 地址信息以 DNS 应答报文返回给 DNS 客户端。
3. DNS 客户端在收到本地名称服务器的 DNS 应答报文后,根据其中给出的根名称服务器地址信息,向对应的根名称服务器再次发送解析 www.google.com 域名的 DNS 请求报文)。
4. 根名称服务器在收到 DNS 查询请求后,通过查询得到 .com 顶级域名所对应的顶级名称服务器,然后把查询到的对应顶级域名信息以一条 DNS 应答报文返回给 DNS 客户端。
5. DNS 客户端在收到根名称服务器的 DNS 应答报文,得到 .com 顶级域名所对应的顶级名称服务器地址后,再次向对应的顶级名称服务器发送一条解析 www.google.com 域名的的 DNS 请求报文。
6. .com 顶级名称服务器在收到 DNS 客户端的 DNS 查询请求报文后,先查询自己的缓存,假设也没有该域名的记录项,则查询 google.com 所对应的二级名称服务器,然后把查询到的对应二级域名信息以一条 DNS 应答报文返回给 DNS 客户端。
7. DNS 客户端在收到 .com 顶级名称服务器的 DNS 应答报文,得到 google.com 二级域名所对应的二级名称服务器地址后,再次向对应的二级名称服务器发送一条解析 www.google.com 域名的 DNS 请求报文。
8. google.com 二级名称服务器在收到 DNS 客户端的 DNS 查询请求报文后,也先查询自己的缓存,假设也没有该域名的记录项,则查询 www.google.com 所对应的权威名称服务器(因为这个名称服务器已包括了整个域名 www.google.com 所在区域),然后把查询到的对应权威域名信息以一条 DNS 应答报文返回给 DNS 客户端。
9. DNS 客户端在收到 google.com 二级名称服务器的 DNS 应答报文,得到 www.google.com 三级域名所对应的权威名称服务器地址后,再次向对应的权威名称服务器发送解析 www.google.com 域名的 DNS 请求报文。
10. 权威名称服务器``www.google.com在收到 DNS 客户端的 DNS 查询请求报文后,在它的 DNS 区域数据库中查找,最终得出了 www.google.com 域名所对应的 IP 地址。然后向 DNS 客户端返回一条 DNS 应答报文。这样 DNS 客户端获取 IP 地址后就可以正常访问这个网站了。
递归查询
  1. 客户端向本机配置的本地名称服务器发出 DNS 域名查询请求。
  2. 本地名称服务器收到请求后,先查询本地的缓存,如果有该域名的记录项,则本地名称服务器就直接把查询的结果返回给客户端;如果本地缓存中没有该域名的记录,则本地名称服务器再以 DNS 客户端的角色发送与前面一样的 DNS 域名查询请求发给根名称服务器。
  3. 根名称服务器收到 DNS 请求后,把所查询得到的所请求的 DNS 域名中顶级域名所对应的顶级名称服务器地址返回给本地名称服务器。
  4. 本地名称服务器根据根名称服务器所返回的顶级名称服务器地址,向对应的顶级名称服务器发送与前面一样的 DNS 域名查询请求。
  5. 顶级名称服务器在收到 DNS 查询请求后,也是先查询自己的缓存,如果有所请求的 DNS 域名的记录项,则直接把对应的记录项返回给本地名称服务器,然后再由本地名称服务器返回给 DNS 客户端,否则向本地名称服务器返回所请求的 DNS 域名中的二级域名所对应的二级名称服务器地址。
  6. 本地名称服务器根据根名称服务器所返回的二级名称服务器地址,向对应的二级名称服务器发送与前面一样的 DNS 域名查询请求。
  7. 二级名称服务器在收到 DNS 查询请求后,也是先查询自己的缓存,如果有所请求的 DNS 域名的记录项,则直接把对应的记录项返回给本地名称服务器,然后再由本地名称服务器返回给 DNS 客户端,否则向本地名称服务器返回所请求的 DNS 域名中的三级域名所对应的三级名称服务器地址。
  8. 就这样本地名称服务器重复步骤 6 和步骤 7 的方法一次次地向三级、四级名称服务器等查询,直到最终的对应域名所在区域的权威名称服务器返回到最终的记录给本地名称服务器。
  9. 然后再由本地名称服务器返回给 DNS 客户,同时本地名称服务器会缓存本次查询得到的记录项。
markdown
假设客户端想要访问自己并不识别的站点www.google.com,那么 DNS 客服端的查询路径如下:

1. DNS 客户端 向所配置的本地名称服务器 dns.google.com 发出解析 www.google.com 域名的 DNS 请求报文。
2. 本地名称服务器收到请求后,先查询本地缓存。假设没有查到该域名对应记录,则本地名称服务器向所配置的根名称服务器 a.rootserver.net 发出解析请求解析 www.google.com 域名的 DNS 请求报文(相当于对本地名称服务器说:“请给我 www.google.com 所对应的 IP 地址”)。
3. 根名称服务器收到 客户端的 DNS 查询请求报文后,通过查询得到.com 顶级域名所对应的顶级名称服务器,然后向本地名称服务器返回一条应答报文(相当说“我不知道www.google.com 域名所对应的 IP 地址,但我现在告诉你 .com 域名所对应的顶级名称服务器地址”)。
4. 本地名称服务器在收到根名称服务器的 DNS 应答报文,得到 .com 顶级域名所对应的顶级名称服务器地址后,再次向对应的顶级名称服务器发送一条请求解析 www.google.com 域名的 DNS 请求报文。
5. .com 顶级名称服务器在收到 DNS 请求报文后,先查询自己的缓存,假设也没有该域名的记录项,则查询 google.com 所对应的二级名称服务器,然后也向本地名称服务返回一条 DNS 应答报文(相当于对本地名称服务器说:“我不知道 www.google.com 域名所对应的 IP 地址,但我现在告诉你 google.com 域名所对应的二级名称服务器地址”。
6. 本地名称服务器在收到 .com 顶级名称服务器的 DNS 应答报文,得到 google.com 二级域名所对应的二级名称服务器地址后,再次向对应的二级名称服务器发送一条请求解析 www.google.com 域名的 DNS 请求报文。
7. google.com 二级名称服务器在收到 DNS 请求报文后,也先查询自己的缓存,假设也没有该域名的记录项,则查询 www.google.com 所对应的权威名称服务器,然后也向本地名称服务器返回一条 DNS 应答报文(相当于本地名称服务器说:“我不知道 www.google.com 域名所对应的 IP 地址,但我现在告诉你 www.google.com 域名所对应的权威名称服务器地址”)。
8. 本地名称服务器在收到 google.com 二级名称服务器的 DNS 应答报文,得到 www.google.com 三级域名所对应的权威名称服务器地址后,再次向对应的权威名称服务器发送一条请求解析 www.google.com 域名的 DNS 请求报文。
9. www.google.com``权威名称服务器在收到 DNS 请求后,在它的 DNS 区域数据库中查找,最终得出了www.google.com 域名所对应的 IP 地址。然后向本地名称服务器返回到条 DNS 应答报文(相当于对本地名称服务器说:“www.google.com 域名的 IP 地址为 xxx.xxx.xxx.xxx”)。
10. 本地名称服务器在收到权威名称服务器的应答报文后,向 DNS 客户端返回一条 DNS 应答报文,告诉 DNS 客户端所得到的www.google.com 域名的 IP 地址。这样 DNS 客户端就可以正常访问这个网站了。

缓存位置

浏览器缓存位置分为四种,其优先级顺序如下:

  • Service Workers
  • Memory Cache
  • Disk Cache
  • Push Cache

当上述四个缓存位置中的缓存都没有命中时,则会向服务器发起请求。

Service Workers

Service Workers 是一个注册在指定源和路径下的事件驱动 worker。它采用 JavaScript 控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。

我们可以通过谷歌开发者工具中的 Application -> Service Workers 查看当前缓存的资源。

Memory Cache

Memory Cache 即内存中的缓存,其特点是容量小、读取高效、持续性短,会随着进程的释放而释放。

memory cache 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP 协议头的约束。当资源被存入内存后,下次同样的请求将不再通过网络,而是直接访问内存,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不再出现 from memory cache 的情况。

那有人会问了,那什么时候资源会被放入 memory 缓存呢

答案是几乎所有的网络请求资源都会根据相关的策略被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。当数据量过大,即使网页不关闭,缓存依然会失效。

memory cache 机制保证了一个页面中如果有两个相同的请求( 例如两个 src 相同的 <img/>,两个 href 相同的 <link/> )都实际只会被请求最多一次,避免浪费。

所以,在内存使用率低、缓存小尺寸资源时,会以 Memory Cache 为优先,否则使用 Disk Cache。

Disk Cache

Disk Cache 即磁盘中的缓存,其特点是容量大、读取缓慢、持续性长,任何资源都能存储到磁盘中。

所以,在内存使用率高、缓存大尺寸资源时,会以 Disk Cache 为优先。

Push Cache

Push Cache 是 HTTP 2.0 中的内容,其缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。

缓存策略

浏览器与服务器通信的方式为应答模式,即是:浏览器发起 HTTP 请求 – 服务器响应该请求,那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。具体过程如下图:

由上图我们可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起 HTTP 请求将缓存过程分为两个部分,分别是强缓存和协商缓存。

强缓存

强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 network 选项中可以看到该请求返回 200 的状态码,并且 size 显示 from disk cache 或 from memory cache;强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

接下来在我们介绍 Expires 和 Cache-Control 之前先看一个示例:

yaml
Cache-Control: max-age=3600 我希望你把这个资源缓存起来,缓存时间是3600秒(1小时)
Expires: Thu, 10 Nov 2020 08:45:11 GMT 到达指定时间过期
Date: Thu, 30 Apr 2020 12:39:56 GMT
Etag: W/"121-171ca289ebf",(后面协商缓存内容)这个资源的编号是W/"121-171ca289ebf"
Last-Modified: Thu, 30 Apr 2020 08:16:31 GMT,(后面协商缓存内容)这个资源的上一次修改时间

Cache-Control 和 Expires 分别是 HTTP/1.1 和 HTTP/1.0 的内容,为了兼容 HTTP/1.0 和 HTTP/1.1,实际项目中两个字段我们都会设置。

浏览器收到这个响应之后就会做下面的事情

  • 浏览器把这次请求得到的响应体缓存到本地文件中
  • 浏览器标记这次请求的请求方法和请求路径
  • 浏览器标记这次缓存的时间是 3600 秒
  • 浏览器记录服务器的响应时间是格林威治时间 2020-04-30 12:39:56

这一次的记录非常重要,它为以后浏览器要不要去请求服务器提供了依据。 之后当客户端收准备再次请求同样的地址时,它突然想起了一件事:我需要的东西在不在缓存里呢?

此时,客户端会到缓存中去寻找是否有缓存的资源,如下

判断缓存是否有效就是通过把 max-age + Date,得到一个过期时间,看看这个过期时间是否大于当前时间,如果是,则表示缓存还没有过期,仍然有效,如果不是,则表示缓存失效。

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires = date。Expires 是 Web 服务器响应消息头字段,在响应 http 请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

Expires: Wed, 07 Dec 2022 03:38:53 GMT 表示资源会在 Wed, 07 Dec 2022 03:38:53 GMT 后过期,需要再次请求。

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。 所以,Expires 字段几乎不被使用了。现在的项目中,我们并不推荐使用 Expires,强缓存功能通常使用 cache-control 字段来代替 Expires 字段。

Cache-Control

在 HTTP/1.1 中,Cache-Control 是最重要的规则,主要用于控制网页缓存。比如当 Cache-Control: max-age = 300 (单位为秒) 时,则代表在这个请求正确返回时间(浏览器也会记录下来)的 5 分钟内再次加载资源,就会命中强缓存。

Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:

属性描述
max-age=30缓存 30 秒后过期,需要重新请求
s-maxage=30覆盖 max-age,作用一样,只在代理服务器中生效
public表示资源可以被浏览器和代理服务器缓存
private表示资源只能被浏览器缓存
no-cache资源被缓存但立即失效,后续进行协商缓存
no-store表示不缓存任何资源
max-stale=3030 秒内,即使缓存过期,也使用该缓存
min-fresh=30希望 30 秒内,获取最新的资源

需要注意的是 Cache-Control 是通用头字段,请求头和响应头中都可以使用。

  • 响应头字段的 Cache-Control 用于告知客户端如何缓存资源。
  • 客户端的 Cache-Control 则是告知服务器需要多新鲜的资源,比如 no-cache 或 max-age=0 表示要最新鲜的资源。
补充

缓存的资源过期;优先级:Cache-Control 优先级高于 Expires,为了兼容,通常两个头部同时设置;浏览器默认行为:其实就算 Response Header 中沒有设置 Cache-Control 和 Expires,浏览器仍然会缓存某些资源,这是浏览器的默认行为,是为了提升性能进行的优化,每个浏览器的行为可能不一致,有些浏览器甚至没有这样的优化。

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。

所谓带缓存标识的请求头就是

yaml
If-Modified-Since: Thu, 30 Apr 2020 08:16:31 GMT  亲,你曾经告诉我,这个资源的上一次修改时间是格林威治时间2020-04-30 08:16:31,请问这个资源在这个时间之后有发生变动吗?
If-None-Match: W/"121-171ca289ebf"  亲,你曾经告诉我,这个资源的编号是W/"121-171ca289ebf,请问这个资源的编号发生变动了吗?

之所以要发两个信息,是为了兼容不同的服务器,因为有些服务器只认 If-Modified-Since,有些服务器只认 If-None-Match,有些服务器两个都认,但是一般来说 If-None-Match 的优先级高于 If-Modified-Since

此时可能会产生两个结果

  • 缓存失效: 那么非常简单,服务器再次给予一个正常的响应(响应码 200 带响应体),同时可以附带上新的缓存指令,浏览器缓存新的内容
  • 缓存有效: 服务器返回 304 重定向,并且响应头带上新的缓存指令,浏览器作出相应缓存动作。

协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag,大家看到这两个 header 应该就和上面说的请求头对应上了吧,接下来我们继续详解。

Last-Modified 和 If-Modified-Since:

Last-Modified 是服务器端在响应请求时用来说明资源的最后修改时间。在协商缓存过程中,浏览器发送的 HTTP 请求中 Header 中会带上 If-Modified-Since 字段,值为缓存资源  Last-Modified 属性的值。

当服务器端接收到带有  If-Modified-Since 的请求时,则会将 If-Modified-Since 的值与被请求资源的最后修改时间做对比。如果相同,说明资源没有新的修改,则响应 HTTP Status Code 304,浏览器会继续使用缓存资源;如果最后修改时间比较新,则说明资源被修改过,则响应 HTTP Status Code 200,并返回最新的资源。

ETag 和 If-None-Match

Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。

补充
Last-Modified 和 ETag 的区别:
  1. 首先在精确度上,Etag 要优于 Last-Modified。
    • Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么他们的 Last-Modified 其实并没有体现出来修改,但是 Etag 每次都会改变确保了精度;
    • 如果是负载均衡的服务器,各个服务器生成的 Last-Modified 也有可能不一致。
  2. 第二在性能上,Etag 要逊于 Last-Modified,毕竟 Last-Modified 只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 hash 值。
  3. 第三在优先级上,服务器校验优先考虑 Etag。
Last-Modified 和 ETag 的相同点:

从流程上来说,两者是一样的,前者是对比的资源最后一次修改时间,后者是对比文件内容的 hash 值是否相同。

启发式缓存

MDN 解释: 对于含有特定头信息的请求,会去计算缓存寿命。比如 Cache-control: max-age=N 的头,相应的缓存的寿命就是 N。通常情况下,对于不含这个属性的请求则会去查看是否包含 Expires 属性,通过比较 Expires 的值和头里面 Date 属性的值来判断是否缓存还有效。如果 max-age 和 expires 属性都没有,找找头里的 Last-Modified 信息。如果有,缓存的寿命就等于头里面 Date 的值减去 Last-Modified 的值除以 10(注:根据 rfc2626 其实也就是乘以 10%)

js
// Date 减去 Last-Modified 值的 10% 作为缓存时间。
// Date:创建报文的日期时间, Last-Modified 服务器声明文档最后被修改时间
response_is_fresh = (Date - Last - Modified) % 10;

总结

实际场景应用缓存策略

频繁变动的资源

yaml
Cache-Control: no-cache

对于频繁变动的资源,首先需要使用 Cache-Control:no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

不常变化的资源

yaml
Cache-Control: max-age=31536000

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

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

补充

一、设置不缓存的方法

  1. html 文件设置 meta;
html
<meta http-equiv="pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
<meta http-equiv="expires" content="Wed, 26 Feb 1997 00:00:00 GMT" />
  1. 服务端响应添加 Cache-Control:no-cache,must-revalidate 指令;
  2. 修改请求头 If-modified-since:0 或 If-none-match;
  3. 请求 url 后增加时间戳;
  4. 服务端设置 Cache-Control:private 指令,防止代理服务器缓存资源

二、 为什么同一个资源有时是 from memory cache 有时是 from disk cache?

Chrome 会根据本地内存的使用率来决定缓存存放在哪,如果内存使用率很高,放在磁盘里面,内存的使用率不高会暂时放在内存里面

三、Cache-Control: max-age=0 和 no-cache 有什么不同?

max-age=0 和 no-cache 应该是从语气上不同。max-age=0 是告诉客户端资源的缓存到期应该向服务器验证缓存的有效性。而 no-cache 则告诉客户端使用缓存前必须向服务器验证缓存的有效性。

本地缓存

本地缓存应该是比较简单了,主要就是 Cookie、session、Web Storage Api( 包括 localStorage 和 sessionStorage ) 以及 indexDB,接下来我们一个一个介绍。

Cookie 最开始并不是用于本地存储的,而是为了弥补 HTTP 在状态管理上的不足:

HTTP 是一个无状态的协议,客户端向服务器发送请求,服务器返回响应,但是下一次发送请求时服务端就无法识别客户端的身份信息,故而产生了 Cookie。

Cookie 本质上是浏览器里面存储的一个很小的文本文件,内部以键值对的方式存储。向同一个域名下发送请求都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,就能拿到客户端的状态。

也就是说,Cookie 的作用就是用来做状态存储的。

Cookie 主要被用于以下三个方面:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息);
  • 个性化设置(如用户自定义设置、主题等);
  • 浏览器行为跟踪(如跟踪分析用户行为等);
  1. 当用户访问 web 服务器后,web 服务器会获取用户的状态并且返回一些数据(cookie)给浏览器,浏览器会自动储存这些数据(cookie)。
  2. 当用户再次访问 web 服务器,浏览器会把 cookie 放到请求报文中发送给 web 服务器,web 服务器就会获取到了用户的状态。
  3. 基于这次用户的状态方便用户进行其他业务的访问,并且 web 服务器可以设置浏览器保存 cookie 的时间,cookie 是有域名的概念,只有访问同一个域名的时候才会把之前相同域名返回的 cookie 携带给该 web 服务器。

缺陷:

  • 容量缺陷:Cookie 的体积上限只有 4KB,只能用来存储少量的信息。
  • 性能缺陷:Cookie 是紧跟域名的,不管域名下面的某个地址需不需要这个 Cookie,它都会携带上完整的 Cookie。这样随着请求数据的增多,很容易造成性能上的浪费。
  • 安全缺陷:由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,并在 Cookie 的有效期内重新发送给服务器。另外,在 HTTPOnly 为 false 的情况下,Cookie 信息能直接通过 JS 脚本读取。
属性说明
keycookie 的键(名称)
valuecookie 的值
max_agecookie 被保存的时间数,单位为秒。
expires具体的过期时间,一个 datetime 对象或 UNIX 时间戳
path限制 cookie 只在给定的路径可用,默认为整个域名下路径都可用
domain设置 cookie 可用的域名,默认是当前域名,子域名需要利用通配符 domain=.当前域名
secure如果设为 True,只有通过 HTTPS 才可以用
httponly如果设为 True,禁止客户端 JavaScript 获取 cookie
  • Secure-  前缀:以Secure- 为前缀的 cookie(其中连接符是前缀的一部分),必须与 secure 属性一同设置,同时必须应用于安全页面(即使用 HTTPS 访问的页面)。
  • Host-  前缀:  以Host- 为前缀的 cookie,必须与 secure 属性一同设置,必须应用于安全页面(即使用 HTTPS 访问的页面),必须不能设置 domain 属性 (也就不会发送给子域),同时 path 属性的值必须为“/”。
http
// 当响应来自于一个安全域(HTTPS)的时候,二者都可以被客户端接受
Set-Cookie: __Secure-ID=123; Secure; Domain=example.com
Set-Cookie: __Host-ID=123; Secure; Path=/

// 缺少 Secure 指令,会被拒绝
Set-Cookie: __Secure-id=1

// 缺少 Path=/ 指令,会被拒绝
Set-Cookie: __Host-id=1; Secure

// 由于设置了 domain 属性,会被拒绝
Set-Cookie: __Host-id=1; Secure; Path=/; domain=example.com
  • 服务端:在收到 HTTP 请求时,服务器可以在响应头里面添加若干个  Set-Cookie 选项,每一个代表一个 Cookie 及其相应配置;
  • 客户端:浏览器收到响应后通常会保存下 Cookie,之后对该服务器每一次请求中都通过 Cookie 请求头部将 Cookie 信息发送给服务器。

如:

服务端响应内容:

http
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

客户端之后的请求内容:

http
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
js
// true表示开启Cookie功能
window.navigator.cookieEnabled;

document.cookie =
  "username=ziming; expires=Thu, 16 Dec 2015 12:00:00 GMT; path=/"; // 设置cookie
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT"; // 删除cookie

session(扩展)

cookie 和 session 都是用来跟踪浏览器用户的身份的方式,不过事先申明,我个人并不认为 seesion 可以算作是浏览器的本地存储方法,因为其是在服务端。

区别:

  1. 保存方式
    • cookie 保存在浏览器端;
    • session 保存在服务器端
  2. 使用原理
    • cookie 机制:如果不在浏览器中设置过期时间,cookie 被保存在内存中,生命周期随浏览器的关闭而结束,这种 cookie 简称会话 cookie。如果在浏览器中设置了 cookie 的过期时间,cookie 被保存在硬盘中,关闭浏览器后,cookie 数据仍然存在,直到过期时间结束才消失。
    • session 机制:当服务器收到请求需要创建 session 对象时,首先会检查客户端请求中是否包含 session id。如果有 session id,服务器将根据该 id 返回对应 session 对象。如果客户端请求中没有 session id,服务器会创建新的 session 对象,并把 session id 在本次响应中返回给客户端。
  3. 存储内容:
    • cookie 只能保存字符串类型,以文本的方式。
    • session 通过类似与 Hashtable 的数据结构来保存,能支持任何类型的对象(session 中可含有多个对象)
  4. 存储的大小:
    • cookie:单个 cookie 保存的数据不能超过 4kb。
    • session 大小理论上没有限制。
  5. 安全性:
    • cookie:针对 cookie 所存在的攻击:Cookie 欺骗,Cookie 截获;
    • session 的安全性大于 cookie。
  6. 应用场景:
    • cookie:
      • 判断用户是否登陆过网站,以便下次登录时能够实现自动登录(或者记住密码)。如果我们删除 cookie,则每次登录必须重新填写登录的相关信息。
      • 保存上次登录的时间等信息。
      • 保存上次查看的页面
      • 浏览计数
    • session:
      • Session 用于保存每个用户的专用信息,变量的值保存在服务器端,通过 SessionID 来区分不同的客户。
      • 网上商城中的购物车
      • 保存用户登录信息
      • 将某些数据放入 session 中,供同一用户的不同页面使用
      • 防止用户非法登录
  7. 缺点:
    • cookie:
      • 大小受限,不能超过 4kb;
      • 用户可以操作(禁用)cookie,使功能受限;
      • 安全性较低;
      • 有些状态不能保存在客户端;
      • 每次访问都要传送 cookie 给服务器,浪费带宽;
      • cookie 数据有路径(path)的概念,可以限制 cookie 只属于某个路径下。
    • session:
      • Session 保存的东西越多,就越占用服务器内存,对于用户在线人数较多的网站,服务器的内存压力会比较大。
      • 依赖于 cookie(session id 保存在 cookie),如果禁用 cookie,则要使用 URL 重写,不安全
      • 创建 session 变量有很大的随意性,可随时调用,不需要开发者做精确地处理,所以,过度使用 session 变量将会导致代码不可读而且不好维护。

localStorage

HTML5 新方法,不过 IE8 及以上浏览器都兼容。

window.localStorage(或直接 localStorage)指向一个可被用于访问当前源( origin )的本地存储空间的  localStorage  对象。

特点:

  • 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的;
  • 同一个源(origin)下的 localStorage 对象是共享的,不能访问不同源的 localStorage 对象(同源策略);
  • 存储对象是简单的键值存储,类似于对象,但是它们在页面加载时保持完整。键和值始终是字符串;
  • 存储大小不同浏览器不一样,一般来说在 5M 左右,查询浏览器 Storage 大小;
  • 当本页操作(新增、修改、删除)了 localStorage 的时候,本页面不会触发 storage 事件,但是同一个域名下的其他所有页面会触发 storage 事件;
  • localStorage 本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡;

使用方法:

可以像访问对象一样访问 Storage,也可以使用其内置方法。

  • 设置:
js
localStorage.colorSetting = "#a4509b";
localStorage["colorSetting"] = "#a4509b";
localStorage.setItem("colorSetting", "#a4509b");

// 对象需要使用 JSON.stringify
obj = { color: "#a4509b" };
localStorage.setItem("colorObj", JSON.stringify(obj));
  • 读取:
js
let cat1 = localStorage.myCat;
let cat2 = localStorage["myCat"];
let cat3 = localStorage.getItem("myCat");

// 读取对象需要使用 JSON.parse
let catObj = JSON.parse(localStorage.getItem("catObj"));
  • 删除:
js
// 删除某一项
localStorage.removeItem("uselessItem");
// 删除所有项
localStorage.clear();

对象属性:

只有一个只读属性:length,返回一个整数,表示存储在  Storage  对象里的数据项(data items)数量。

storage 事件:

当 Storage 对象发生变化时,会触发 storage 事件。

但是,导致 localStorage 对象发生变化的页面不会触发该事件,而是同一域名下的其他所有页面触发。IE 浏览器除外,它会在所有页面触发 storage 事件。

如:

在 test.cn/a 页面(a 页面)下添加事件监听:

js
window.addEventListener("storage", callback);

在 test.cn/b 页面(b 页面)下改变 Storage 对象(增加、删除、修改):

localStorage.setItem('test', 'xxx');

则 a 页面会执行 callback 函数,而如果在 a 页面下改变 Storage 对象,则 callback 函数不会被执行。

回调函数中的参数 event,是一个 StorageEvent 对象,主要有以下实用属性:

属性类型描述
keyStringStorage 对象被改变(增加、删除、修改)的属性的 key
oldValueAny先前被改变的值,如果是添加值,则 oldValue=null
newValueAny改变后的新值,如果是删除值,则 newValue=null
urlString改变 Storage 对象的页面的 url

注:如果调用的是 localStorage.clear(),则 key、oldValue 和 newValue 都是 null。

Session Storage

将数据保存在 session 对象中。

所谓 session,是指用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间。session 对象可以用来保存在这段时间内所要求保存的任何数据。

sessionStorage  允许你访问一个,对应当前源的  session Storage  对象。它与  localStorage  相似。

相同点

  • 容量:sessionStorage 的容量上线也为 5MB。
  • 只存储在客户端,默认不参与与服务器端的通讯。
  • 接口封装:除了名字变化,sessionStorage 的存储方式和操作方式均和 localStorage 一样。
  • localStorage 和 sessionStorage 只能存储字符串类型。

不同点有以下几处:

localStoragesessionStorage
存储时间永久存储,除非手动删除在页面会话结束时会被清除
对象共享协议、域名、端口号都相同的页面共享同一个 localStorage 对象除了协议、域名、端口号都相同外,还需要在同一窗口下,才能共享一个 sessionStorage 对象
storage 事件其他共享 localStorage 的页面会触发 storage 事件每个标签的 sessionStorage 都是隔离的,因此它们无法通信;sessionStorage 的 storage 事件仅在同一选项卡上的其他 iframe 框架中触发。
  • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
  • 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文, 这点和 session cookies 的运行方式不同。
  • 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage。
  • 关闭对应浏览器窗口(Window)/ tab,会清除对应的 sessionStorage。

使用方法和属性与 localStorage 相同,只是将 localStorage 改为 sessionStorage 而已

indexedDB

当数据量不大时,我们可以通过 SessionStorage 或者 LocalStorage 来进行存储,但是当数据量较大,或符合一定的规范时,我们可以使用 indexedDB 数据库来进行数据的存储,indexedDB 数据库存储理论上没有大小的限制。

概念

IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。而 IndexedDB 提供了这种场景的解决方案。

IndexedDB 为什么取代 Web SQL:

WebSQL 是以前浏览器上使用的数据库,不过已经废弃了,接下来分析一下:

Web SQL 是基于 SQLite 的真正意义上的关系型数据库,(SQLite 是遵守 ACID 的轻型的关系型数据库管理系统);而 IndexedDB 是 NoSQL(非关系型数据库),使用键值对存储数据,且结构不固定,非常类似 JavaScript 中的纯对象。

“关系型数据库”对一致性要求非常严格,例如要写入 100 个数据,前 99 个成功了,结果第 100 个不合法,此时事务会回滚到最初状态。这样保证事务结束和开始时候的存储数据处于一致状态。非常适合银行这种对数据一致性要求非常高的场景。但是,这种一致性保证是牺牲了一部分性能的。但是,对于微博,QQ 空间这类 web2.0 应用,一致性却不是显得那么重要,比方说朋友 A 看我的主页和朋友 B 看我的主页信息有不一样,没什么大不了的,有个几秒差异很 OK 的。虽然这类应用对一致性要求不高,但对性能要求却很高,因为读写实在太频繁了。如果使用“关系型数据库”,一致性的好处没怎么收益,反而性能问题比较明显,此时,不保证一致性但性能更优的“非关系型数据库”则更合适。同时,由于“非关系型数据库”的数据结构不固定,非常容易扩展。由于对于社交网站,需求变动那是一日三餐常有的事,添加新字段在所难免,“非关系型数据库”轻松上阵,如果使用“关系型数据库”,多半要数据大变动,要好好琢磨琢磨了。从气质上讲,“关系型数据库”稳重持久,“非关系型数据库”迅速灵动。在前端领域,Web SQL Database 是“关系型数据库”,indexedDB 是“非关系型数据库”。

Web SQL 直接写 SQL 语句,需要把 JS 对象转换成关系型的字符串语句;而 indexedDB 直接 JS 对象入库,通过调用 API 实现数据的增删改查。

IndexedDB 使用场景

所有的场景都基于客户端需要存储大量数据的前提下:

  • 数据可视化等界面,大量数据,每次请求会消耗很大性能。
  • 即时聊天工具,大量消息需要存在本地。
  • 其它存储方式容量不满足时,不得已使用 IndexedDB

IndexedDB 特点

(1) 非关系型数据库(NoSql)

我们都知道 MySQL 等数据库都是关系型数据库,它们的主要特点就是数据都以一张二维表的形式存储,而 Indexed DB 是非关系型数据库,主要以键值对的形式存储数据。

(2)持久化存储

cookie、localStorage、sessionStorage 等方式存储的数据当我们清楚浏览器缓存后,这些数据都会被清除掉的,而使用 IndexedDB 存储的数据则不会,除非手动删除该数据库。

(3)异步操作

IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他的操作,这与 localstorage 形成鲜明的对比,后者是同步的。

(4)支持事务

IndexedDB 支持事务(transaction),这意味着一系列的操作步骤之中,只要有一步失败了,整个事务都会取消,数据库回滚的事务发生之前的状态,这和 MySQL 等数据库的事务类似。

(5)同源策略

IndexedDB 同样存在同源限制,每个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

(6)存储容量大

这也是 IndexedDB 最显著的特点之一了,这也是不用 localStorage 等存储方式的最好理由。

IndexedDB 重要概念讲解

仓库 objectStore

IndexedDB 没有表的概念,它只有仓库 store 的概念,大家可以把仓库理解为表即可,即一个 store 是一张表。

索引 index

在关系型数据库当中也有索引的概念,我们可以给对应的表字段添加索引,以便加快查找速率。在 IndexedDB 中同样有索引,我们可以在创建 store 的时候同时创建索引,在后续对 store 进行查询的时候即可通过索引来筛选,给某个字段添加索引后,在后续插入数据的过成功,索引字段便不能为空。

游标 cursor

游标是 IndexedDB 数据库新的概念,大家可以把游标想象为一个指针,比如我们要查询满足某一条件的所有数据时,就需要用到游标,我们让游标一行一行的往下走,游标走到的地方便会返回这一行数据,此时我们便可对此行数据进行判断,是否满足条件。

【注意】:IndexedDB 查询不像 MySQL 等数据库方便,它只能通过主键、索引、游标方式查询数据。

事务

IndexedDB 支持事务,即对数据库进行操作时,只要失败了,都会回滚到最初始的状态,确保数据的一致性。

IndexedDB 实操

IndexedDB 所有针对仓库的操作都是基于事务的。

创建或连接数据库
js
/**
 * 打开数据库
 * @param {object} dbName 数据库的名字
 * @param {string} storeName 仓库名称
 * @param {string} version 数据库的版本
 * @return {object} 该函数会返回一个数据库实例
 */
function openDB(dbName, version = 1) {
  return new Promise((resolve, reject) => {
    //  兼容浏览器
    var indexedDB =
      window.indexedDB ||
      window.mozIndexedDB ||
      window.webkitIndexedDB ||
      window.msIndexedDB;
    let db;
    // 打开数据库,若没有则会创建
    const request = indexedDB.open(dbName, version);

    // 数据库打开成功回调
    request.onsuccess = function (event) {
      db = event.target.result; // 数据库对象
      console.log("数据库打开成功");
      resolve(db);
    };

    // 数据库打开失败的回调
    request.onerror = function (event) {
      console.log("数据库打开报错");
    };

    // 数据库有更新时候的回调
    request.onupgradeneeded = function (event) {
      // 数据库创建或升级的时候会触发
      console.log("onupgradeneeded");
      db = event.target.result; // 数据库对象
      var objectStore;
      // 创建存储库
      objectStore = db.createObjectStore("signalChat", {
        keyPath: "sequenceId", // 这是主键
        // autoIncrement: true // 实现自增
      });

      // 创建索引,在后面查询数据的时候可以根据索引查
      objectStore.createIndex("link", "link", { unique: false });
      objectStore.createIndex("sequenceId", "sequenceId", { unique: true });
      objectStore.createIndex("messageType", "messageType", {
        unique: false,
      });
    };
  });
}

我们将创建数据库的操作封装成了一个函数,并且该函数返回一个 promise 对象,使得在调用的时候可以链式调用,函数主要接收两个参数:数据库名称、数据库版本。函数内部主要有三个回调函数,分别是:

  • onsuccess:数据库打开成功或者创建成功后的回调,这里我们将数据库实例返回了出去。
  • onerror:数据库打开或创建失败后的回调。
  • onupgradeneeded:当数据库版本有变化的时候会执行该函数,比如我们想创建新的存储库(表),就可以在该函数里面操作,更新数据库版本即可。
插入数据
js
/**
 * 新增数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} data 数据
 */
function addData(db, storeName, data) {
  var request = db
    .transaction([storeName], "readwrite") // 事务对象 指定表格名称和操作模式("只读"或"读写")
    .objectStore(storeName) // 仓库对象
    .add(data);

  request.onsuccess = function (event) {
    console.log("数据写入成功");
  };

  request.onerror = function (event) {
    console.log("数据写入失败");
  };
}

IndexedDB 插入数据需要通过事务来进行操作,插入的方法也很简单,利用 IndexedDB 提供的 add 方法即可,这里我们同样将插入数据的操作封装成了一个函数,接收三个参数,分别如下:

  • db:在创建或连接数据库时,返回的 db 实例,需要那个时候保存下来。
  • storeName:仓库名称(或者表名),在创建或连接数据库时我们就已经创建好了仓库。
  • data:需要插入的数据,通常是一个对象

【注意】: 插入的数据是一个对象,而且必须包含我们声明的索引键值对。

通过主键读取数据
js
/**
 * 通过主键读取数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {string} key 主键值
 */
function getDataByKey(db, storeName, key) {
  return new Promise((resolve, reject) => {
    var transaction = db.transaction([storeName]); // 事务
    var objectStore = transaction.objectStore(storeName); // 仓库对象
    var request = objectStore.get(key); // 通过主键获取数据

    request.onerror = function (event) {
      console.log("事务失败");
    };

    request.onsuccess = function (event) {
      console.log("主键查询结果: ", request.result);
      resolve(request.result);
    };
  });
}

主键即刚刚我们在创建数据库时声明的 keyPath,通过主键只能查询出一条数据。

通过游标查询数据
js
/**
 * 通过游标读取数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 */
function cursorGetData(db, storeName) {
  let list = [];
  var store = db
    .transaction(storeName, "readwrite") // 事务
    .objectStore(storeName); // 仓库对象
  var request = store.openCursor(); // 指针对象
  // 游标开启成功,逐行读数据
  request.onsuccess = function (e) {
    var cursor = e.target.result;
    if (cursor) {
      // 必须要检查
      list.push(cursor.value);
      cursor.continue(); // 遍历了存储对象中的所有内容
    } else {
      console.log("游标读取的数据:", list);
    }
  };
}
通过主键删除数据
js
/**
 * 通过主键删除数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {object} id 主键值
 */
function deleteDB(db, storeName, id) {
  var request = db
    .transaction([storeName], "readwrite")
    .objectStore(storeName)
    .delete(id);

  request.onsuccess = function () {
    console.log("数据删除成功");
  };

  request.onerror = function () {
    console.log("数据删除失败");
  };
}
更新数据

IndexedDB 更新数据较为简单,直接使用 put 方法,值得注意的是如果数据库中没有该条数据,则会默认增加该条数据,否则更新。有些小伙伴喜欢更新和新增都是用 put 方法,这也是可行的。

js
/**
 * 更新数据
 * @param {object} db 数据库实例
 * @param {string} storeName 仓库名称
 * @param {object} data 数据
 */
function updateDB(db, storeName, data) {
  var request = db
    .transaction([storeName], "readwrite") // 事务对象
    .objectStore(storeName) // 仓库对象
    .put(data);

  request.onsuccess = function () {
    console.log("数据更新成功");
  };

  request.onerror = function () {
    console.log("数据更新失败");
  };
}
关闭数据库

当我们数据库操作完毕后,建议关闭它,节约资源。

js
/**
 * 关闭数据库
 * @param {object} db 数据库实例
 */
function closeDB(db) {
  db.close();
  console.log("数据库已关闭");
}
删除数据库
js
/**
 * 删除数据库
 * @param {object} dbName 数据库名称
 */
function deleteDBAll(dbName) {
  console.log(dbName);
  let deleteRequest = window.indexedDB.deleteDatabase(dbName);
  deleteRequest.onerror = function (event) {
    console.log("删除失败");
  };
  deleteRequest.onsuccess = function (event) {
    console.log("删除成功");
  };
}

总结

IndexedDB 数据库没有我们想象的那么复杂,了解了它的几个基本概念,上手还是很快的,无非就是增删改查等等,虽然可能开发中用的少,但是了解一下不至于真正用到的时候两眼抓瞎,如果还需要细致了解的话可以看看这篇文章:前端本地存储数据库 IndexedDB 完整教程

总结

session 我说过应该不属于浏览器的本地存储机制,不过值得讲一下。

结语

上面就是有关 http 缓存和本地缓存的全部内容,希望能在大家面试的时候帮到大家。

参考文献

  1. 深入理解浏览器缓存原理
  2. 浏览器缓存缓存策略(看完就懂)
  3. DNS 递归查询与迭代查询的详细流程
  4. HTTP 缓存
  5. 彻底弄懂浏览器缓存策略
  6. HTTP 缓存的三种方式详解
  7. 浏览器本地存储的四种方式介绍及区别
  8. 对浏览器本地存储的全面理解
  9. 前端本地存储数据库 IndexedDB 完整教程

备案号:闽ICP备2024028309号-1