前端缓存详解

前端缓存是我们日常开发中非常重要的一部分,利用缓存我们可以做很多的事情。例如:
- 利用浏览器缓存用于保存数据,用作性能优化
- 利用http缓存减少与服务器之间的通信,降低服务器压力和请求时间
了解完前端缓存能做的事情后,接下来了解一下前端缓存的组成和实现吧~
前端缓存分类
首先,前端缓存可以分为 HTTP缓存
和 浏览器缓存
。
- HTTP缓存负责发起Http请求时
约定
保存指定对应的数据。HTTP缓存又分为强缓存
和协商缓存
。 - 浏览器缓存指的是浏览器提供给我们用于数据保存的内存空间,浏览器缓存又有很多种,分为:
Cookie
,localStorage
,sessionStorage
,IndexDB
。
介绍完前端缓存的分类,接下来详细了解一下每一个缓存的作用和使用。
HTTP缓存
强缓存
首先来看一下一个HTTP请求的流程
可以看到,在向服务器请求之前,会先向浏览器缓存查找,是否有这个缓存,查找缓存的根据是url
。
如果有缓存则立即返回
数据。如果没有找到缓存或者缓存已经过期,此时需要分情况讨论:
- 如果缓存已经过期,http请求会携带缓存标识到服务器中,服务器返回信息后将数据和标识缓存在浏览器缓存中
- 如果没有查找到,则直接发起HTTP请求,返回后将数据和标识保存到缓存中
设置强缓存
强缓存是通过设置HTTP请求头来设置的,具体的字段有:
Expires: HTTP/1.0 使用的字段,值为资源的过期时间。利用服务器时间与客户端作对比来判断缓存是否过期。(如果服务器与客户端时间不同步,则会造成缓存失效)
Cache-Control: Cache-Control字段同样也可以设置强缓存,而且优先级比Expires高。Cache-Control使用max-age来设置缓存过期时间,即在HTTP请求中返回一个资源有效的秒数,避免了Expires时间比较存在的问题。除此之外,Cache-Control还提供资源缓存策略。
Cache-Control
取值[1]:
字段名 | 作用 |
---|---|
public | 所有内容都将被缓存(客户端和代理服务器都可以缓存) |
private | 所有内容只有客户端可以缓存 (默认取值) |
no-cache | 客户端缓存内容,但是是否使用缓存则需要经过协商缓存决定 |
no-store | 所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 |
max-age=x | 缓存内容将在 x 秒后失效 |
来看一个简单的🌰:
首先简单搭起一个服务
1 | const http = require('http'); |
前端声明一个简单的html文件,文档中有一张图片,而且调用ajax去请求/file
这个接口
1 | <html> |
浏览器收到Cache-Control
后,就会存放在浏览器缓存中,下一次调用时如果未过期则会使用浏览器的缓存。如图所示
返回结果:
可以看到,后端返回了一个Cache-Control的字段,这个就是用于强缓存的字段,如果浏览器检测到有此标识,则将数据保存到浏览器缓存中。
那么我们要如何判别是否触发了强缓存呢?很简单,我们刷新一下页面看一下。
当我们再次刷新页面时,接口的size变成了disk cache
。表示这个缓存存放在硬盘中。此时,除非缓存时间已经过,或者手动请求浏览器缓存。访问改接口都会返回浏览器保存在本地的数据。
除了存放在硬盘中,强缓存还可以存放在 内存(memory cache) 中,可以看到图片的缓存为memory cache
- disk cache: 存放在硬盘中的缓存,读取需要进行I/O操作
- memory cache: 存放在内存中的缓存,速度快,但是有时间限制。浏览器一但关闭,缓存就会消失
看到上文我们可以知道,强缓存也分为disk cache和memory cache,那么什么时候会使用memory cache呢?
这取决于使用浏览器的缓存策略:通常一些小的图片,部分js文件会存放在内存中,css文件则存放在硬盘中。
除了上面两种缓存,还有下面几种缓存:
- Service Worker:一个独立于当前页面,可以在浏览器后台运行的脚本。具体可以看这里
- Push Cache: 推送缓存是HTTP/2中的内容,当
disk
,memory
,service worker
都没有被使用的时候才会触发,具体的Push Cache可以看这里
缓存的执行顺序:Service Worker -> Memory Cache -> Disk Cache -> Push Cache
协商缓存
HTTP除了强缓存,还有协商缓存,当强缓存失效后,浏览器会携带缓存标识去请求资源,服务器根据缓存标识来决定是否使用缓存。协商缓存调用过程如下
可以看到,强缓存的优先级比协商缓存高,当强缓存失效时,才会触发协商缓存。而协商缓存原理是:通过缓存标识去请求服务器对比该资源是否有改变过,如果没有改变过,则可以继续使用强缓存失效的数据。
也就是说,协商缓存主要有两种情况:
- 标识与服务器的表示一致,返回304,请求浏览器缓存得到数据
- 表示与服务器的不一致,请求拿到对应资源,返回200
使用协商缓存
使用协商缓存与强缓存一样,都是通过HTTP请求头实现。设置协商缓存的字段有两个,分别是:
Last-Modified
: 返回资源时,该资源的最后修改时间Etag
: 表示当前资源的唯一标识(由服务器生成)
当浏览器接收到有协商缓存字段请求时,会将该标识保存在浏览器缓存中,并在下一次请求该资源时带上该标识,浏览器在带上请求时与服务器设置的字段的不一样,他们分别是:
If-Modified-Since
: 对应的是Last-Modifyed。通过对比时间判断资源是否有修改If-None-Match
: 对应的是Etag。通过对比该资源的唯一标识,判断资源是否有修改
来看一下协商缓存的实现
1 | // 服务器端 |
可以看到,在读取JS文件,我们使用Last-Modified
触发协商缓存,读取图片文件时,使用Etag
来触发协商缓存。对应地分别使用If-Modified-Since
和If-None-Match
来捕捉浏览器发送过来的标识。
访问 localhost:8888/index.html, 返回结果如下
可以看到这两个资源都触发了协商缓存,资源都是从浏览器缓存中得到的。
小结
- 强缓存是直接存放在
内存
或硬盘
中的缓存,通过HTTP请求头的Expires
和Cache-Control
设置强缓存。存储在内存的缓存速度非常快,但是空间有限。存储在硬盘的缓存读取时需要I/O操作,所以比内存缓存要慢 - 协商缓存是当强缓存失效时触发的缓存,通过HTTP请求头的
Last-Modified
和Etag
设置协商缓存。服务器对应地需要获取请求头中的If-Modified-Since
和If-None-Match
来判断标识是否一致。一致则返回304
状态码,浏览器收到304状态吗后直接取浏览器缓存中的数据 - 强缓存优先级高于协商缓存
浏览器缓存
Cookie
什么是Cookie
首先来了解一下什么是Cookie
HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。Cookie实际上是一小段的文本信息(key-value格式)。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。[5]
上面引用提到:
- Cookie是服务器向客户端返回的,浏览器接收到后会把Cookie保存起来。
- 当浏览器再次请求该域名时,会把Cookie也带上交给服务器。
- 服务器检测到cookie后,来确认用户信息。
举个🌰:
1 | const http = require('http'); |
浏览器访问 index.html ,浏览器发现后Set-Cookie
字段后,将他保存到缓存中
在浏览器中也可以看到它保存在Cookies
中了
现在我们来请求同一个源中的/file.js
接口
1 | const request = new XMLHttpRequest() |
可以看到Request Headers
中自动带上了Cookie
字段,我们再来修改一下服务端代码
1 | const http = require('http'); |
上面代码中,修改了一下/file.js
的逻辑:
- 进入请求时,需要先获取cookie,cookie进入时是以字符串形式给到服务器端,需要手动转化成对象
- 转化cookie后,判断用户是否正确,如果正确则返回对应的数据,如果错误则返回对应提示。
可见,cookie可以保存用户状态。除了服务器端可以获取到cookie,客户端也可以拿到cookie。只需调用document.cookie
即可拿到cookie字符串。
Cookie属性
属性名 | 作用 |
---|---|
Key=Value | 键值对 |
Path | 指定哪些路径可以接受Cookie |
Domain | 指定哪些域名可以接受Cookie |
Secure | 设置后 Cookie 只应通过被HTTPS协议加密过的请求发送给服务端 |
HttpOnly | 防止客户端修改和访问Cookie,避免XSS攻击 |
Expires | 指定Cookie过期时间 |
SameSite | Cookie 允许服务器要求某个cookie在跨站请求时不会被发送,防止CSRF攻击 |
Cookie很小,大约只有4KB
,所以通常只会用来存放一些用户信息,如果想用稍微大一点的浏览器缓存,可以使用浏览器提供的Storage
Storage
浏览器Storage是HTML5的新引入的,可以在浏览器端保存数据,分为LocalStorage
和SessionStorage
。
两者调用方法,大小和API都是一致的,大约有5MB。唯一不同的是SessionStorage
在关闭页面后就会消失,而LocalStorage
除非用户手动清除,不然会一致存在。
注意,Storage只在同源页面下共享。
IndexDB
IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。该 API 使用索引实现对数据的高性能搜索。虽然 Web Storage 在存储较少量的数据很有用,但对于存储更大量的结构化数据来说力不从心。[7]
IndexDB是在前端运行的一个数据库系统,想要了解更多点击这里
IndexDB存储数据举例:
1 |
|
可以看到,
执行结果:
总结
HTTP缓存
强缓存
- 触发:设置HTTP请求头的
expires
或cache-control
字段,cache-control
的优先级比expires
高 - 缓存位置:
硬盘
或内存
中 - 读取缓存顺序:Service Worker -> Memory Cache -> Disk Cache -> Push Cache
- 触发:设置HTTP请求头的
协商缓存
- 触发:设置HTTP请求头的
last-modified
或Etag
字段。last-modified
通常是数据的最后更改时间;Etag
为数据在服务器的唯一标识 - 捕捉缓存:服务器端需要获取HTTP请求头的
if-modified-since
或if-none-match
字段。判断请求携带过来标识于对应数据的标识是否一致,一致则返回304。浏览器判断到304状态码后从浏览器缓存中读取数据 - 优先级:
Etag
优先级比last-modify
高
- 触发:设置HTTP请求头的
浏览器缓存
Cookie
- 概念:Cookie是一小段的文本信息,由服务器主动发送到浏览器中。
- 触发:服务器通过请求头的
Set-Cookie
字段将数据发送给浏览器。浏览器收到后会将数据保存起来。下一次请求对应的域名后会自动把Cookie放到请求头的Cookie
字段中。
Storage
- 概念:HTML5新增的特效,可以在浏览器端保存数据,分为
sessionStorage
和localStorage
,sessionStorage
在页面关闭时会消失。localStorage
除非用户手动请求,不然一直会存在浏览器中。 - 缓存共享:只在同源的页面中共享缓存
- 概念:HTML5新增的特效,可以在浏览器端保存数据,分为
IndexDB
- 概念:前端用于保存大量结构化数据的数据库。