浏览器缓存
缓存位置
- 内存缓存(memory cache)
- 快速读取(内存缓存将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取)
- 进程关闭时数据会被清除
- 不请求网络资源,资源存在内存中一般
JS和图片文件会存入内存 - 状态码:
200(from memory cache)
- 硬盘缓存(disk cache)
- 写入硬盘文件进行
I/O操作 - 进程关闭时数据不会被清除
- 速度比
memory cache慢 - 不请求网络资源,资源存在磁盘中一般非脚本会存在磁盘中,如
css - 状态码:
200(from disk cache)
- 写入硬盘文件进行
- 代理服务器缓存(server worker)
- 可以拦截处理页面的所有网络请求
- 仅
HTTPS下可用、存在兼容问题 - 状态码:
200(from service worker)
我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储 和 持久性储存。
短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
持久性存储,可以分为 浏览器端 与 服务器端
浏览器
cookie通常用于存储用户身份,登录状态等http中自动携带, 体积上限为4K, 可自行设置过期时间localStorage / sessionStorage: 长久储存/窗口关闭删除, 体积限制为4~5MindexDB
服务器:
- 分布式缓存
redis
- 分布式缓存
数据库
提示:如果平常有遇到过缓存的坑或者很好的利用缓存,可以讲解一下自己的使用场景。如果没有使用注意过缓存问题你也可以尝试讲解一下和我们息息相关的 Webpack 构建(每一次构建静态资源名称的
hash值都会变化),它其实就跟缓存相关
缓存分为强缓存和协商缓存。强缓存不过服务器,协商缓存需要过服务器,协商缓存返回的状态码是 304。两类缓存机制可以同时存在,强缓存的优先级高于协商缓存。当执行强缓存时,如若缓存命中,则直接使用缓存数据库中的数据,不再进行缓存协商。
强缓存

Expires(HTTP1.0):Exprires的值为服务端返回的数据到期时间。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差。另一方面,Expires 是 HTTP1.0 的产物,故现在大多数使用Cache-Control替代
缺点:使用的是绝对时间,如果服务端和客户端的时间产生偏差,那么会导致命中缓存产生偏差。
Cache-Control(HTTP1.1):有很多属性,不同的属性代表的意义也不同
private:客户端可以缓存public:客户端和代理服务器都可以缓存max-age=t:缓存内容将在 t 秒后失效no-cache:需要使用协商缓存来验证缓存数据,可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)no-store:所有内容都不会缓存,永远都不要在客户端存储资源,永远都去原始服务器去获取资源
请注意
no-cache指令很多人误以为是不缓存,这是不准确的,no-cache的意思是可以缓存,但每次用应该去向服务器验证缓存是否可用。no-store才是不缓存内容。当在首部字段Cache-Control有指定max-age指令时,比起首部字段 Expires,会优先处理max-age指令。命中强缓存的表现形式:Firefox 浏览器表现为一个灰色的 200 状态码。Chrome 浏览器状态码表现为 200 (from disk cache)或是 200 OK (from memory cache)
协商缓存(对比缓存)
协商缓存需要进行对比判断是否可以使用缓存。浏览器第一次请求数据时,服务器会将缓存标识与数据一起响应给客户端,客户端将它们备份至缓存中。再次请求时,客户端会将缓存中的标识发送给服务器,服务器根据此标识判断。若未失效,返回 304 状态码,浏览器拿到此状态码就可以直接使用缓存数据了
Last-Modified:服务器在响应请求时,会告诉浏览器资源的最后修改时间if-Modified-Since:浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可- 如果真的被修改:那么开始传输响应一个整体,服务器返回:
200 OK - 如果没有被修改:那么只需传输响应
header,服务器返回:304 Not Modified Etag:服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成的唯一标识(生成规则由服务器决定)If-Match:条件请求,携带上一次请求中资源的ETag,服务器根据这个字段判断文件是否有新的修改If-None-Match: 再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现If-None-Match则与被请求资源的唯一标识进行对比。
但是实际应用中由于
Etag的计算是使用算法来得出的,而算法会占用服务端计算的资源,所有服务端的资源都是宝贵的,所以就很少使用Etag了
- 浏览器地址栏中写入
URL,回车浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿(最快) F5就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就胆胆襟襟的发送一个请求带上If-Modify-sinceCtrl+F5告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作
总结
- 服务端判断客户端资源,是否和服务端资源一样。
- 一致则返回
304,否则返回200和最新的资源
资源标识:
- 在
Response Headers中,有两种。 Last-Modified:资源的最后修改时间。Etag:资源的唯一标识(一个字符串,类似于人类的指纹)

服务端拿到
if-Modified-Since之后拿这个时间去和服务端资源最后修改时间做比较,如果一致则返回304,不一致(也就是资源已经更新了)就返回 200 和新的资源及新的Last-Modified

其实
Etag和Last-Modified一样的,只不过Etag是服务端对资源按照一定方式(比如contenthash)计算出来的唯一标识,就像人类指纹一样,传给客户端之后,客户端再传过来时候,服务端会将其与现在的资源计算出来的唯一标识做比较,一致则返回304,不一致就返回200和新的资源及新的Etag
两者比较:
- 优先使用
Etag。 Last-Modified只能精确到秒级。- 如果资源被重复生成,而内容不变,则
Etag更精确。

三种刷新操作对 http 缓存的影响
- 正常操作:地址栏输入
url,跳转链接,前进后退等 - 手动刷新:
f5,点击刷新按钮,右键菜单刷新 - 强制刷新:
ctrl + f5,shift+command+r
- 正常操作:强制缓存有效,协商缓存有效
- 手动刷新:强制缓存失效,协商缓存有效
- 强制刷新:强制缓存失效,协商缓存失效
缓存场景
对于大部分的场景都可以使用强缓存配合协商缓存解决,但是在一些特殊的地方可能需要选择特殊的缓存策
- 对于某些不需要缓存的资源,可以使用
Cache-control: no-store,表示该资源不需要缓存 - 对于频繁变动的资源,可以使用
Cache-Control: no-cache并配合ETag使用,表示该资源已被缓存,但是每次都会发送请求询问资源是否更新 - 对于代码文件来说,通常使用
Cache-Control: max-age=31536000并配合策略缓存使用,然后对文件进行指纹处理,一旦文件名变动就会立刻下载新的文件
讲讲 304
如果客户端发送了一个带条件的
GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个304状态码
强缓存、协商缓存什么时候用哪个
因为服务器上的资源不是一直固定不变的,大多数情况下它会更新,这个时候如果我们 还访问本地缓存,那么对用户来说,那就相当于资源没有更新,用户看到的还是旧的资 源;所以我们希望服务器上的资源更新了浏览器就请求新的资源,没有更新就使用本地 的缓存,以最大程度的减少因网络请求而产生的资源浪费。
缓存总结
缓存分为两种:强缓存和协商缓存,根据响应的
header内容来决定。
| 获取资源形式 | 状态码 | 发送请求到服务器 | |
|---|---|---|---|
| 强缓存 | 从缓存取 | 200(from cache) | 否,直接从缓存取 |
| 协商缓存 | 从缓存取 | 304(not modified) | 是,通过服务器来告知缓存是否可 用 |
- 强缓存相关字段有
expires,cache-control。如果cache-control与expires同时存在的话,cache-control的优先级高于expires。 - 协商缓存相关字段有
Last-Modified/If-Modified-Since,Etag/If-None-Match
cookie 和 localSrorage、session、indexDB 的区别
| 特性 | cookie | localStorage | sessionStorage | indexDB |
|---|---|---|---|---|
| 数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
| 数据存储大小 | 4K | 5M | 5M | 无限 |
| 与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
从上表可以看到,
cookie已经不建议用于存储。如果没有大量数据存储需求的话,可以使用localStorage和sessionStorage。对于不怎么改变的数据尽量使用localStorage存储,否则可以用sessionStorage存储。
对于 cookie,我们还需要注意安全性
| 属性 | 作用 |
|---|---|
value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 |
http-only | 不能通过 JS访问 Cookie,减少 XSS攻击 |
secure | 只能在协议为 HTTPS 的请求中携带 |
same-site | 规定浏览器不能在跨域请求中携带 Cookie,减少 CSRF 攻击 |
IndexedDB
IndexedDB 是一个运行在浏览器上的非关系型数据库,用于在客户端存储大量结构化数据
IndexedDB 的特点
- 存储空间大(一般来说不少于
250MB甚至没有上限) - 支持存储二进制数据(
ArrayBuffer和Blob) - 键值对储存
- 同源限制
- 执行的操作是异步执行,以免阻塞应用程序
- 是一个事务型数据库系统
API 使用
打开/创建一个 IndexedDB 数据库,并指定数据库的版本号 (版本号只能为整数)
const request = window.indexedDB.open('myDatabase', 1)
let db
// 成功回调
request.onsuccess = function (event) {
// 获取 indexedDB 实例
db = event.target.result
// 也可以使用 request.result 获取 indexedDB 实例
console.log('连接 IndexedDB 成功')
}
// 失败回调
request.onerror = function () {
console.log('连接 IndexedDB 失败')
}const request = window.indexedDB.open('myDatabase', 1)
let db
// 成功回调
request.onsuccess = function (event) {
// 获取 indexedDB 实例
db = event.target.result
// 也可以使用 request.result 获取 indexedDB 实例
console.log('连接 IndexedDB 成功')
}
// 失败回调
request.onerror = function () {
console.log('连接 IndexedDB 失败')
}创建一个对象仓库(类似于数据库中的表)
// upgradeneeded 事件会在初始化数据库或版本发生更新时被调用
request.onupgradeneeded = function (event) {
const db = event.target.result
// 创建对象仓库并指定主键
const objectStore = db.createObjectStore('userInfo', {
keyPath: 'id',
autoIncrement: false,
})
console.log('创建对象仓库成功')
/**
* 定义存储对象的数据项
* 第一个参数是创建的索引名称,可以为空
* 第二个参数是索引使用的关键名称,可以为空
* 第三个参数是可选配置参数,可以不传,常用参数之一就是 unique ,表示该字段是否唯一,不能重复
*/
objectStore.createIndex('id', 'id', {
unique: true,
})
objectStore.createIndex('name', 'name')
}// upgradeneeded 事件会在初始化数据库或版本发生更新时被调用
request.onupgradeneeded = function (event) {
const db = event.target.result
// 创建对象仓库并指定主键
const objectStore = db.createObjectStore('userInfo', {
keyPath: 'id',
autoIncrement: false,
})
console.log('创建对象仓库成功')
/**
* 定义存储对象的数据项
* 第一个参数是创建的索引名称,可以为空
* 第二个参数是索引使用的关键名称,可以为空
* 第三个参数是可选配置参数,可以不传,常用参数之一就是 unique ,表示该字段是否唯一,不能重复
*/
objectStore.createIndex('id', 'id', {
unique: true,
})
objectStore.createIndex('name', 'name')
}添加数据
// 创建事务指并定表格名称和读写权限
const transaction = db.transaction(['userInfo'], 'readwrite')
// 获取 Object Store 对象
const objectStore = transaction.objectStore('userInfo')
/* 添加数据 */
objectStore.add({ id: 1, name: 'test' })// 创建事务指并定表格名称和读写权限
const transaction = db.transaction(['userInfo'], 'readwrite')
// 获取 Object Store 对象
const objectStore = transaction.objectStore('userInfo')
/* 添加数据 */
objectStore.add({ id: 1, name: 'test' })获取数据
const transaction = db.transaction(['userInfo'], 'readonly')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.get(1)
objectStoreRequest.onsuccess = function () {
console.log('获取数据', objectStoreRequest.result)
}const transaction = db.transaction(['userInfo'], 'readonly')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.get(1)
objectStoreRequest.onsuccess = function () {
console.log('获取数据', objectStoreRequest.result)
}修改数据
const transaction = db.transaction(['userInfo'], 'readwrite')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.get(1)
objectStoreRequest.onsuccess = function () {
const data = objectStoreRequest.result
data.name = 'maomao'
objectStore.put(data)
}const transaction = db.transaction(['userInfo'], 'readwrite')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.get(1)
objectStoreRequest.onsuccess = function () {
const data = objectStoreRequest.result
data.name = 'maomao'
objectStore.put(data)
}删除数据
const transaction = db.transaction(['userInfo'], 'readwrite')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.delete(1)
objectStoreRequest.onsuccess = function () {
console.log('删除成功')
}const transaction = db.transaction(['userInfo'], 'readwrite')
const objectStore = transaction.objectStore('userInfo')
const objectStoreRequest = objectStore.delete(1)
objectStoreRequest.onsuccess = function () {
console.log('删除成功')
}在日常开发中可以使用如下类库简化代码量
- localForage 一个提供
name:value的简单语法的客户端数据存储垫片,基于IndexedDB实现,并在不持支IndexedDB的浏览器中自动回退到WebSQL和localStorage - Dexie.js 对
IndexedDB的封装,通过提供更友好和简单语法进行快速的编码开发 - PouchDB 对
IndexedDB的封装,通过提供更友好和简单语法进行快速的编码开发
什么是 Cookie
- HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过
cookie或者session去实现 - cookie 存储在客户端:
cookie是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上 - cookie 是不可跨域的: 每个
cookie都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是domain)
| 属性 | 说明 |
|---|---|
name=value | 键值对,设置 Cookie 的名称及相对应的值,都必须是字符串类型。如果值为 Unicode 字符,需要为字符编码。如果值为二进制数据,则需要使用 BASE64 编码。 |
domain | 指定 cookie 所属域名,默认是当前域名 |
path | 指定 cookie 在哪个路径(路由)下生效,默认是 '/'。如果设置为 /abc,则只有 /abc 下的路由可以访问到该 cookie,如:/abc/read。 |
maxAge | cookie 失效的时间,单位秒。如果为整数,则该 cookie 在 maxAge 秒后失效。如果为负数,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。如果为 0,表示删除该 cookie 。默认为 -1。- 比 expires 好用。 |
expires | 过期时间,在设置的某个时间点后该 cookie 就会失效。一般浏览器的 cookie 都是默认储存的,当关闭浏览器结束这个会话的时候,这个 cookie 也就会被删除 |
secure | 该 cookie 是否仅被使用安全协议传输。安全协议有 HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效 |
httpOnly | 如果给某个 cookie 设置了 httpOnly 属性,则无法通过 JS 脚本 读取到该 cookie 的信息,但还是能通过 Application 中手动修改 cookie,所以只是在一定程度上可以防止 XSS 攻击,不是绝对的安全 |
什么是 Session
session是另一种记录服务器和客户端会话状态的机制session是基于cookie实现的,session存储在服务器端,sessionId会被存储到客户端的cookie中

session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的
Session - 请求返回时将此
Session的唯一标识信息SessionID返回给浏览器 - 浏览器接收到服务器返回的
SessionID信息后,会将此信息存入到Cookie中,同时Cookie记录此SessionID属于哪个域名 - 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在
Cookie信息,如果存在自动将Cookie信息也发送给服务端,服务端会从Cookie中获取SessionID,再根据SessionID查找对应的Session信息,如果没有找到说明用户没有登录或者登录失效,如果找到Session证明用户已经登录可执行后面操作。
根据以上流程可知,
SessionID是连接Cookie和Session的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
Cookie 和 Session 的区别
- 安全性:
Session比Cookie安全,Session是存储在服务器端的,Cookie是存储在客户端的。 - 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,
Session可以存任意数据类型。 - 有效期不同:
Cookie可设置为长时间保持,比如我们经常使用的默认登录功能,Session一般失效时间较短,客户端关闭(默认情况下)或者Session超时都会失效。 - 存储大小不同: 单个
Cookie保存的数据不能超过4K,Session可存储数据远高于Cookie,但是当访问量过多,会占用过多的服务器资源。
Cao Note