通过学长的分享,我学到了

cookie、localStorage、sessionStorage、和IndexedDB 四者之间有什么区别

这四者都是浏览器提供的客户端存储机制,但它们在设计目的、容量、生命周期、访问方式和应用场景上有显著区别:

以下是详细的对比表格:

特性 Cookie localStorage sessionStorage IndexedDB
设计目的 维持客户端与服务器间的状态 持久化存储客户端数据 会话级别的临时存储 客户端存储大量结构化数据
存储容量 约 4KB (单个域名下) 约 5-10MB (不同浏览器有差异) 约 5-10MB (不同浏览器有差异) 非常大 (通常 >250MB,甚至硬盘空间50%)
生命周期 可设置过期时间 (默认浏览器关闭时过期) 永久存储,需手动清除 会话结束清除 (标签页关闭) 永久存储,需手动清除
数据操作 通过 document.cookie 字符串操作 (同步) 简单的 Key-Value API (同步) 简单的 Key-Value API (同步) 异步 API,支持事务、索引、游标、复杂查询
数据格式 字符串 (需自行序列化/反序列化复杂数据) 字符串 (需使用 JSON.stringify/parse) 字符串 (需使用 JSON.stringify/parse) 支持 JS对象、二进制数据 (ArrayBuffer, Blob)
HTTP请求 每次请求自动携带 (同域名下) 不参与 不参与 不参与
访问范围 遵循同源策略,可设置 Domain/Path 作用域 同源策略 (协议+域名+端口相同) 同源策略,且仅在当前标签页内 同源策略,支持对象存储、索引
安全性 可设置 HttpOnly(防XSS)、Secure(HTTPS) 易受XSS攻击 易受XSS攻击 易受XSS攻击
主要应用场景 用户认证(Token)、会话跟踪、个性化设置 长期保存的用户偏好设置、缓存数据 表单临时草稿、单次会话状态 离线应用、大型数据缓存、富媒体文件存储

深入解析

  1. Cookie

    • 核心作用: 作为HTTP协议状态管理的补充,每次HTTP请求 都会自动携带在同源请求头中(Cookie 头),服务器可读写。这是它与其它三者的本质区别。
    • 容量限制: 非常小(约4KB),超出限制可能被截断或拒绝。
    • 生命周期: 可设置 ExpiresMax-Age 属性指定过期时间。未设置则为"会话Cookie",浏览器关闭即失效。
    • 访问方式: 通过 document.cookie API 读写,操作繁琐(需解析字符串)。
    • 安全: 可设置 Secure (仅HTTPS传输)、HttpOnly (禁止JavaScript访问,防XSS窃取)、SameSite (控制跨站发送)。
    • 缺点: 容量小、性能差(每次请求携带)、API难用、易受CSRF攻击(需配合其他措施)。
  2. localStorage

    • 核心作用: 持久化存储 较大量的纯客户端数据,不参与任何网络通信。
    • 容量: 大得多(通常5-10MB),满足大部分客户端存储需求。
    • 生命周期: 数据永久存储 在客户端,除非用户手动清除浏览器缓存或代码调用 localStorage.clear() / removeItem()。刷新页面、关闭浏览器甚至重启电脑都不会丢失。
    • 访问方式: 简单的同步 Key-Value API (getItem, setItem, removeItem, clear, key, length)。
    • 数据格式: 仅存储字符串。存储对象需 JSON.stringify(),读取需 JSON.parse()
    • 作用域: 同源策略。相同协议、域名、端口下的页面共享同一个 localStorage。
    • 事件: 监听 storage 事件可在同源的其他窗口/标签页间同步数据变更。
    • 缺点: 同步API可能阻塞渲染(大容量时)、纯字符串存储、无高级查询、易受XSS攻击。
  3. sessionStorage

    • 核心作用: 存储当前浏览器标签页会话期间需要的数据。
    • 容量: 与 localStorage 相同(通常5-10MB)。
    • 生命周期: 会话结束即清除 。具体指:
      • 关闭当前标签页浏览器窗口
      • 新标签页或窗口 中打开同源页面会创建新的独立 sessionStorage。
      • 刷新页面不会清除 sessionStorage。
    • 访问方式/数据格式/作用域: API 与 localStorage 完全相同 (getItem, setItem 等),也是 Key-Value 字符串存储,遵循同源策略。但作用域仅限于创建它的标签页,即使是同源的其他标签页也无法访问。
    • 应用: 非常适合存储表单填写过程中的临时数据、单页面应用的路由状态等,这些数据在标签页关闭后无需保留。
    • 缺点: 同 localStorage (API同步、字符串、无查询、XSS风险),且数据非持久化。
  4. IndexedDB

    • 核心作用: 在客户端存储大量结构化数据 (甚至二进制大对象),提供高性能索引查询异步事务操作。
    • 容量: 非常大(浏览器通常允许使用大量磁盘空间,如Chrome一般至少250MB,最多可达硬盘空间的50%以上)。
    • 生命周期: 持久化存储,类似 localStorage,需手动删除或清除浏览器数据才会消失。
    • 访问方式: 异步API ,基于事务操作,避免阻塞主线程。支持游标遍历、索引查询。API相对复杂。
    • 数据格式: 支持存储 JavaScript 对象、数组、File/Blob、ArrayBuffer 等复杂数据,无需手动序列化为字符串。
    • 数据结构: 类似 NoSQL 数据库。有数据库 概念,数据库包含多个对象存储 ,对象存储类似于表,存储键值对数据。可创建索引实现高效查询。
    • 应用: 离线应用(缓存大量数据)、富媒体应用(缓存图片/音视频)、需要复杂查询和大量数据的客户端应用、渐进式Web应用(PWA)。
    • 缺点: API 复杂(学习曲线陡峭)、低版本浏览器兼容性问题(需polyfill)、开发调试相对麻烦。

总结与选择建议

  • 需要与服务器通信的状态/认证信息? ➡️ Cookie (务必设置 HttpOnlySecure)。
  • 需要在客户端持久化存储少量简单数据(如用户设置)? ➡️ localStorage
  • 只需要在当前标签页会话期间临时存储数据(如表单草稿)? ➡️ sessionStorage
  • 需要存储大量结构化数据、二进制文件、支持高效查询、构建离线应用? ➡️ IndexedDB

理解它们的关键差异,根据数据的大小、结构、生命周期需求、是否需要网络传输、是否需要复杂查询等因素来选择最合适的存储方案。

浏览器缓存机制

1、缓存位置

从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络

  1. Service Worker
  2. Memory Cache
  3. Disj Cache
  4. Push Cache

1.1 Service Worker

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。.

Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。

1.2 Memory Cache

Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。

当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存

内存缓存中有一块重要的缓存资源是preloader相关指令(例如 )下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。

需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。

1.3Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。

浏览器会把哪些文件丢进内存中?哪些丢进硬盘中? 关于这点,网上说法不一,不过以下观点比较靠得住:

对于大文件来说,大概率是不存储在内存中的,反之优先 当前系统内存使用率高的话,文件优先存储进硬盘

1.4 Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。 Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及。

这里推荐阅读Jake Archibald的 HTTP/2 push is tougher than I thought 这篇文章,文章中的几个结论: - 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差 - 可以推送 no-cache 和 no-store 的资源 - 一旦连接被关闭,Push Cache 就被释放 - 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。 - Push Cache 中的缓存只能被使用一次 - 浏览器可以拒绝接受已经存在的资源推送 - 你可以给其他域名推送资源

1.5网络请求

  • 如果所有缓存都没有命中的话,那么只能发起请求来获取资源了。
  • 那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,接下来我们就来学习缓存策略这部分的内容

2.缓存策略

通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的

2.1 强缓存

什么是强缓存?

强缓存 是浏览器缓存机制中最直接、最高效的一种。它的核心思想是:在缓存有效期内,浏览器根本不会向服务器发送请求,直接强制使用本地缓存资源。

整个过程发生在浏览器内部,无需与服务器进行任何通信,因此速度极快,能极大提升页面加载性能和用户体验。

它的名字中的"强"就体现在这个"强制性"上------只要认为缓存有效,就绝不询问服务器。

强缓存如何工作?------ 基于缓存过期策略

浏览器如何判断一个缓存的资源是否在有效期内呢?这主要通过查看服务器在响应头中设置的缓存规则来实现。主要有两个 HTTP 头字段控制强缓存: 强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control 。强缓存表示在缓存期间不需要请求,state code 为 200

Expires

Expires 是 HTTP/1.0 时代用来控制缓存的字段。

  • 定义 :它指定了一个绝对的过期时间。浏览器在收到这个响应头后,会把这个资源和它的过期时间一起缓存起来。下次请求时,浏览器会先检查本地系统时间,如果系统时间还没到这个过期时间,就直接使用缓存。

  • 语法示例

    http

    yaml 复制代码
    Expires: Wed, 21 Oct 2026 07:28:00 GMT
  • 缺点

    1. 依赖客户端时间:如果用户手动修改了自己电脑的系统时间,缓存判断就会出错(例如,用户把时间改到未来,缓存会立即失效;改到过去,则缓存可能一直有效)。
    2. 绝对时间,配置复杂:需要后端计算一个精确的 GMT 时间字符串。

由于其明显的缺陷,在 HTTP/1.1 中,引入了功能更强大、更科学的 Cache-Control 来替代它。现在通常作为备用方案,用于向下兼容

Cache-control

Cache-Control 是 HTTP/1.1 的产物,它采用了一种更灵活的方式------相对时间 ,并且提供了非常丰富的指令来控制缓存行为。它的优先级高于 Expires

Cache-Control 既可以出现在响应头 中,也可以出现在请求头中,但最常用的是响应头。以下是它的常用指令:

在响应头中 (由服务器设置)
  • max-age=<seconds>

    • 最重要的指令 。表示资源可以被缓存的最长时间(单位:秒)。这是一个相对时间,从响应被生成的那一刻 开始计时(即收到响应的时间),避免了 Expires 的时间同步问题。
    • 示例Cache-Control: max-age=3600 表示这个资源在 1 小时内都是新鲜的,浏览器在这 1 小时内再次请求都会直接读缓存。
  • public

    • 表明响应可以被任何对象(包括:发送请求的客户端,代理服务器如 CDN,等)缓存。
  • private

    • 表明响应只能被单个用户(的浏览器)缓存,不能在代理服务器(如 CDN)上共享缓存。通常用于存储用户的私有信息。
  • no-cache

    • 这个名字有点误导性 。它不意味着"不缓存",而是强制要求在使用缓存的副本之前,必须向服务器发起验证(Validation) ,确认资源是否过期(通常用 ETagLast-Modified 字段)。如果验证通过(资源未修改),则服务器返回 304,浏览器就可以使用缓存。这个过程也叫协商缓存
    • 可以理解为"缓存,但每次用之前要问一下服务器"
  • no-store

    • 这才是真正的"不缓存" 。它规定不能存储客户端磁盘或任何中间代理服务器的缓存。每次请求都必须从服务器重新获取完整的响应。用于高度敏感的数据(如密码、支付页面)。
  • s-maxage=<seconds>

    • max-age 类似,但仅适用于共享缓存 (如 CDN)。优先级高于 max-age
  • must-revalidate

    • 一旦缓存过期(比如超过了 max-age 的时间),必须去服务器验证,而不能直接使用过期的缓存。如果无法连接服务器进行验证,则必须返回一个 504 Gateway Timeout 错误,而不是使用旧的缓存。
在请求头中 (由浏览器设置)
  • no-cache:告诉服务器,我不要缓存的副本,请给我最新的资源。
  • max-age=0:含义类似于 no-cache,表示我愿意使用缓存,但它必须是"年龄"为0的,即刚刚从服务器获取的。

强缓存的完整流程

强缓存与协商缓存共同构成了浏览器缓存的完整决策链。其核心规则是:先判断强缓存是否生效,若生效则直接使用;若不生效,则进入协商缓存流程。

2.2 协商缓存 ### 什么是协商缓存?

协商缓存 是一种浏览器缓存机制。它的核心思想是:由服务器来验证缓存是否有效

当浏览器希望使用一个可能已经缓存的资源时,它会向服务器发送一个请求。但这个请求不会直接要求完整的数据,而是会携带一些"证据"(上次缓存时服务器返回的验证信息),询问服务器:"我本地缓存的这个版本还能不能用?"。

服务器收到请求后,会检查这些"证据":

  • 如果证据有效 (资源未修改),则返回一个极短的 304 Not Modified 响应,告诉浏览器:"缓存有效,直接用它吧!"。
  • 如果证据失效 (资源已修改),则返回一个正常的 200 OK 响应,并在响应体中携带全新的资源内容

这个过程就像你去图书馆借书:

  1. 你问管理员:"我之前借的《XXX》第一版,现在有新版吗?"(发送带有验证信息的请求

  2. 管理员查了一下记录:

    • "没有,还是第一版。"(304,直接用你手上的旧书
    • "有的,这是新出的第二版。"(200,把新书给你

协商缓存的关键技术头(Headers)

协商缓存主要通过两组 HTTP 头字段来实现:

1. Last-Modified & If-Modified-Since
  • 工作原理

    1. 第一次请求 :服务器在响应头中加上 Last-Modified: GMT时间,告诉浏览器这个资源最后的修改时间。

    2. 浏览器缓存 :浏览器将资源和这个 Last-Modified 值一起缓存起来。

    3. 再次请求 :浏览器在请求头中带上 If-Modified-Since: [之前收到的Last-Modified值],询问服务器资源是否在这个时间之后被修改过。

    4. 服务器验证 :服务器比较资源的当前修改时间和 If-Modified-Since 的值。

      • 如果时间一致(未修改) -> 返回 304 Not Modified
      • 如果时间不一致(已修改) -> 返回 200 OK 和新的资源内容,并更新 Last-Modified 头。
  • 缺点

    • 精度只到秒级,如果一秒内多次修改,无法识别。
    • 文件可能只是被重新生成(touch 命令),内容其实没变,但修改时间变了,会导致缓存失效。
2. ETag & If-None-Match

为了解决 Last-Modified 的缺陷,HTTP/1.1 引入了 ETag(Entity Tag,实体标签)。

  • 工作原理

    1. 第一次请求 :服务器通过某种算法(如对资源内容计算哈希值)为资源生成一个唯一标识符,在响应头中加上 ETag: "哈希值或版本号"

    2. 浏览器缓存 :浏览器将资源和这个 ETag 值一起缓存起来。

    3. 再次请求 :浏览器在请求头中带上 If-None-Match: [之前收到的ETag值],询问服务器资源的标识符是否改变。

    4. 服务器验证 :服务器重新计算当前资源的 ETag,并与 If-None-Match 的值进行比较。

      • 如果 ETag 匹配(未修改) -> 返回 304 Not Modified
      • 如果 ETag 不匹配(已修改) -> 返回 200 OK 和新的资源内容,并更新 ETag 头。
  • 优点

    • 精度极高,能精确感知内容的变化。
    • 可以处理那些修改时间改变但内容不变的情况。

ETag 的优先级高于 Last-Modified 。如果请求中同时发送了 If-None-MatchIf-Modified-Since,服务器会优先验证 If-None-Match

协商缓存的完整流程

结语

希望大佬们指点使我成长

相关推荐
顾辰逸you几秒前
uniapp--咸虾米壁纸(三)
前端·微信小程序
北鸟南游4 分钟前
用现有bootstrap的模板,改造成nuxt3项目
前端·bootstrap·nuxt.js
前端老鹰6 分钟前
JavaScript Intl.RelativeTimeFormat:自动生成 “3 分钟前” 的国际化工具
前端·javascript
梦想CAD控件6 分钟前
(在线CAD插件)网页CAD实现图纸表格智能提取
前端·javascript·全栈
木子雨廷24 分钟前
Flutter 开发一个plugin
前端·flutter
重生之我是一名前端程序员26 分钟前
websocket + xterm 前端实现网页版终端
前端·websocket
sorryhc28 分钟前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js
uhakadotcom42 分钟前
NPM与NPX的区别是什么?
前端·面试·github
GAMC1 小时前
如何修改node_modules的组件不被install替换?可以使用patch-package
前端
页面仔Dony1 小时前
webpack 与 Vite 深度对比
前端·前端工程化