浏览器存储
本文最后更新于 2025年8月11日 晚上
参考:
https://juejin.cn/post/6844903516826255373
https://juejin.cn/post/6844903989096497159
https://juejin.cn/post/6844904021308735502#heading-13
前端本地存储方式有三种,分别是 Cookie、WebStorage、IndexedDB。
Cookie
Cookie 最开始被设计出来并不是来做本地存储的,而是为了弥补 HTTP 在状态管理上的不足。HTTP 协议是一个无状态协议,客户端向服务器发请求,服务器返回响应,故事就结束了,但是下次发请求如何让服务端知道客户端是谁呢?在这种背景下就产生了 Cookie。
Cookie 是 HTTP 协议的一部分,也是服务器发送到用户浏览器并保存在本地的一小块数据,内部以键值对的方式来存储。向同一个域名下发送请求会携带相同的 Cookie,服务器拿到 Cookie 进行解析以便能拿到客户端的状态。在 HTTP 中有:Cookie,在浏览器中有:cookie。
作用
Cookie 主要用于以下三个方面:
- 会话状态管理
如用户登录状态、购物车、游戏分数或其他需要记录的信息。 - 个性化设置
如用户自定义设置、主题和其他设置。 - 浏览器行为跟踪
如跟踪分析用户行为等。
Cookie 曾一度用于客户端数据的存储,因为当时并没有其他合适的存储办法而作为唯一的存储手段,但现在有 WebStorage 和 IndexedDB。同时,Cookie 也有着以下缺陷:
- 容量缺陷
Cookie 的体积上限只有 4KB,只能用来存储少量的信息。 - 性能缺陷
Cookie 紧跟域名,不管域名下面的某个地址需不需要 Cookie,请求时都会携带完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费,因为请求会携带很多不必要的内容。 - 安全缺陷
由于 Cookie 以纯文本的形式在浏览器和服务器当中传递,所以很容易被第三方截获或者篡改。在 Cookie 的有效期内重新发送给服务器是相当危险的。
属性
协议匹配 Secure
Secure 属性控制禁止通过 HTTP 协议传输 Cookie(防止中间人窃取),仅允许 HTTPS 协议。
1 | |
域名匹配 Domain
Domain 属性用于限制能访问修改 Cookie 的域名,不用于限制端口以及协议。默认情况下,不同的域名不能直接访问彼此 Cookie,但通过合理设置 Domain 属性可以实现单向或双向共享。
对于 Domain 属性,可以设置为当前域或者祖父域(但不能到顶级域),不能设置为子域或者其他域(设置了也无效),但也可以不设置此属性。不设置此属性时,只有当前域可以访问修改该 Cookie;显式设置此属性为某个域后,设置的域及其所有子域可以访问修改该 Cookie。如:
对于 a.com 站点的 Cookie:
- 可以不设置
Domain
效果:只有a.com可以访问修改 - 可以设置
Domain=a.com(等同Domain=.a.com)
效果:a.com、sub.a.com、api.a.com、sub.api.a.com都可以访问修改 - 不可以设置
Domain=api.a.com、Domain=b.com、Domain=com(等同Domain=.com)
对于 api.a.com 站点的 Cookie:
- 可以不设置
Domain
效果:只有api.a.com可以访问修改 - 可以设置
Domain=api.a.com
效果:api.a.com、sub.api.a.com可以访问修改,a.com、sub.a.com不可以访问修改 - 可以设置
Domain=a.com(等同Domain=.a.com)
效果:a.com、sub.a.com、api.a.com、sub.api.a.com都可以访问修改 - 不可以设置
Domain=sub.api.a.com、Domain=sub.a.com、Domain=api.b.com
对于 sub.api.a.com站点的 Cookie:
- 可以不设置
Domain
效果:只有sub.api.a.com可以访问修改 - 可以设置
Domain=sub.api.a.com、Domain=api.a.com、Domain=a.com
具体效果略 - 不可以设置略
具体效果略
1 | |
路径匹配 Path
Path 属性用于限制能访问修改 Cookie 的路径,默认为当前路径。对于 a.com 站点,如果设置了 Set-Cookie: temp=123; Domain=a.com; Path=/admin; 那么:
a.com/admin可以用此 Cookiea.com/user不可以用此 Cookieapi.a.com/admin可以用此 Cookieapi.a.com/user不可以用此 Cookie
站点匹配 SameSite
SameSite 属性控制在跨站请求(Cross-site request)时是否携带 Cookie,用于防止 CSRF 攻击。该属性共有三种值,默认值为 Lax:
| 值 | 意义 | 适用场景 |
|---|---|---|
| Strict | 完全禁止跨站携带,只有同站请求才带 Cookie,跨站的所有请求(包括 GET)都不带 | 强安全,如银行、后台管理系统 |
| Lax | 同站请求会带;跨站请求只有安全的 GET 顶级导航带(表单 POST 不会带) | 登录后的页面跳转(防止 CSRF,同时允许常规跳转) |
| None | 所有请求都会带 Cookie(包括跨站 POST、iframe、图片加载等),但必须同时设置 Secure(仅限 HTTPS) | CDN、SSO、广告跟踪等 |
需要注意:
- 主域相同的站点就是同站(same-site)(
a.com和sub.a.com和api.a.com都算同站) - 跨站是指主域不同(
a.com→b.com) - 如果
SameSite=None,浏览器要求必须加Secure,否则直接忽略 Cookie
分区存储 Partitioned
Partitioned 属性解决第三方 Cookie 在隐私安全下的存储问题,特别是在浏览器逐渐限制第三方 Cookie 的背景下。
传统第三方 Cookie(例如在 siteA.com 里嵌入 iframe 加载 siteB.com 内容)可以跨网站共享,这对广告追踪等行为非常方便,但也带来了隐私泄露和跨站跟踪风险。为了保护用户隐私,Chrome、Firefox、Safari 等浏览器正在逐步阻止第三方 Cookie,但有些合法场景也需要第三方 Cookie,比如跨站嵌入的支付网关、CDN 登录状态共享、多站点业务集群下的用户会话保持等。
开启 Partitioned 属性后,让第三方 Cookie 隔离到特定的第一方站点上下文中,避免跨站追踪,但又能在嵌入的场景下工作。假设有个站点被两个不同的站点使用:shopA.com 嵌入了 cdn.com,shopB.com 也嵌入了 cdn.com:
- 没有 Partitioned
cdn.com的第三方 Cookie 会在shopA.com和shopB.com间共享,导致跨站追踪风险
- 有 Partitioned
- 在
shopA.com里访问cdn.com时,Cookie 被分配到(cdn.com, shopA.com)分区 - 在
shopB.com里访问cdn.com时,Cookie 被分配到(cdn.com, shopB.com)分区
- 在
这样 cdn.com 在两个不同站点上下文里的 Cookie 是完全独立的,无法互相读取或跟踪。而且:
- 存储:浏览器会把 Cookie 存在一个“第一方上下文分区”下,而不是
cdn.com的全局 Cookie 下 - 读取:只有在相同的第一方上下文中,才能读取这个 Cookie
- 安全性:就算同一个第三方域名被不同网站引用,也不会共享 Cookie 数据,防止广告跨站追踪
需要注意的是,使用 Partitioned 属性的必要条件有四个:
- 设置
SameSite=None(第三方 Cookie 必须允许跨站发送) - 设置
Secure(必须通过 HTTPS 才能传输才能保证安全性) - 禁止设置
Domain(分区 Cookie 指定作用域会导致设置失败) - 仅在嵌套上下文中有效(如 iframe 等),在主窗口设置无效
1 | |
存储管理 Priority
Priority 属性用于管理浏览器存储空间不足时 Cookie 的清理优先级。虽然非官方标准,但在存储优化中扮演关键角色。该属性共有三种值,默认值为 Medium:
| 值 | 优先级 | 适用场景 |
|---|---|---|
| Low | 最先清理 | 分析跟踪、广告标识符 |
| Medium | 其次清理 | 用户偏好设置、功能标记 |
| High | 最后清理 | 会话令牌、身份验证信息 |
1 | |
需要注意:
- 这个属性不会让 Cookie 永不过期,只是在“被迫删掉时”尽量保留
- 如果 Cookie 已经过期(
Expires或Max-Age到期),优先级不会保它 - 它和
SameSite、Secure、HttpOnly等属性互不影响 - 在不支持的浏览器上,它会被当作普通未知属性直接忽略
防窃取 HttpOnly
HttpOnly 属性禁止 JS 代码访问 Cookie(document.cookie 无法读取),用于防止 XSS 攻击。
1 | |
有效期 Expires&Max-Age
通过设置 Expires 属性(指定过期日期和时间)或 Max-Age 属性(指定秒数后过期),可以指定 Cookie 过期时间使其在一段时间后失效。Chrome 从 104 版本开始,限制 Cookie 的最大存储期限为 400 天,未设置 Expires 或 Max-Age 的 Cookie 称为会话 Cookie,在浏览器关闭时会被清除。
1 | |
使用
可通过 document.cookie 获取全部 Cookie。Cookie 是一段字符串,是键值对的形式:
1 | |
原生操作起来有些麻烦,可引入封装好的库进行使用,比如 js-Cookie:
1 | |
区别
Cookie 和 Session 都是普遍用来跟踪浏览用户身份的会话方式。
- Cookie 数据存放在客户端,Session 数据放在服务器端,而且后者是依赖于 Cookie 实现的。
- Cookie 本身并不安全,考虑到安全应当使用 Session。
- Session 会在一定时间内保存在服务器上。如果访问量比较大,会比较消耗服务器的性能。如果考虑到减轻服务器性能方面的开销,应当使用 Cookie。
- 单个 Cookie 保存的数据大小不能超过 4K,很多浏览器都限制一个域名最多保存 50 个 Cookie。可以将登陆等重要信息存放到 Session,其他信息如果需要保留,可以存放到 Cookie。
WebStorage
使用 Cookie 时受到种种限制,最关键是容量太小和无法持久化存储。在 HTML5 的标准下,出现了 WebStorage(又分为 localStorage 和 sessionStorage)。WebStorage 是浏览器基于 HTML5 标准自身的实现,跟 HTTP 无关。
作用域
WebStorage 的作用域规则与 Cookie 不同,它严格遵循浏览器的同源策略(Same-Origin Policy),不同的源之间无法互相访问对方的存储数据。
WebStorage 下的 localStorage 和 sessionStorage 的区别在于,在浏览器的同源但不同的标签页,可以共享 localStorage 数据但无法共享 sessionStorage 数据。localStorage 是一个域名的本地存储(永久存储),sessionStorage 是一个域名的会话存储(关闭页面后消失)。
使用
localStorage 和 sessionStorage 都是 WebStorage 类型实例,使用方法一样。注意事项:
- 写入时如果超出容量会报错,之前保存的数据不会丢失
- 存储容量快满时
getItem()方法性能会急剧下降 - 存储的都是字符串(想要存储对象需调用
JSON.stringify()方法转为 JSON 数据类型;想要获取数据需调用JSON.parse()方法再解析 JSON 数据类型成对象)
1 | |
对比
在浏览器开发者面板的 Application 栏可以看到对应的具体信息。
不同:
| 种类 | 大小 | 有效期 | HTTP 请求 | 使用场景 |
|---|---|---|---|---|
| Cookie | 4KB | 可设置失效时间,默认关浏览器失效 | 每次网络请求都携带 | 识别用户登录 |
| localStorage | 5MB | 除非被手动清除,否则永久保存 | 不与服务端通信 | 持久化缓存数据,可以跨页面传递内容、页面默认偏好配置 |
| sessionStorage | 5MB | 仅在当前网页会话有效,关闭页面后失效 | 不与服务端通信 | 保存临时数据,可以用于对表单信息维护,存储本次浏览记录 |
相同:
- 存储数据类型都是字符串。
- 都受到跨域限制。
IndexedDB
IndexedDB 是运行在浏览器中的非关系型数据库,本质上是数据库,而且不是和 WebStorage 的 5M 在一个量级,理论上这个容量没有上限。关于它的使用,MDN 教程文档已经非常详尽。
分析一下 IndexedDB 的一些重要特性,除了拥有数据库本身的特性,比如支持事务,存储二进制数据等等,还有这样一些特性需要格外注意:
- 键值对存储。内部采用对象仓库存放数据,在这个对象仓库中数据采用键值对的方式来存储。
- 异步操作。数据库的读写属于 I/O 操作, 浏览器中对异步 I/O 提供了支持。
- 受同源策略限制。即无法访问跨域的数据库。