skip to content
月与羽

浏览器存储方式

/ 17 min read

🧠 一、本地存储(Local Storage):持久化的“备忘录”

Local Storage 是一种简单、持久的键值对(key-value)存储机制,数据会一直保留在用户的浏览器中,除非被用户或代码主动清除。

核心特征:

  • 生命周期永久性。数据不会因浏览器关闭或电脑重启而消失。
  • 存储容量:较大,通常为 5MB 左右(因浏览器而异)。
  • 数据格式:只能存储字符串。任何非字符串数据在存入前都必须通过 JSON.stringify() 序列化,取出后通过 JSON.parse() 反序列化。
  • 作用域:遵循同源策略(Same-Origin Policy)。只有同协议、同域名、同端口的页面才能访问。
  • API:简单直观,同步执行。

适用场景:

  • 用户偏好设置:如网站主题(暗色/亮色模式)、语言选择。
  • 持久化用户界面状态:如记住用户上次折叠的面板、一个不那么重要的表单草稿。
  • 缓存非敏感数据:缓存一些不常变动的 API 响应,减少不必要的网络请求。

优点:

  • API 简单易用,上手快。
  • 存储容量远大于 Cookie,能满足多数基本需求。

缺点:

  • API 是同步的:读写操作会阻塞主线程,对于大量或频繁的操作可能影响页面性能。
  • 无法直接存储复杂数据结构:需要手动进行 JSON 序列化和反序列化。
  • 无法被 Web Workers 访问:限制了其在后台线程中的使用。
  • 安全性较低:容易受到 XSS 攻击,恶意脚本可以轻易读写 Local Storage 中的所有数据。

代码示例(存储对象):

// 准备一个对象
const userSettings = {
theme: 'dark',
notifications: {
enabled: true,
level: 'important'
}
};
// 1. 存储:使用 JSON.stringify() 将对象转换为字符串
localStorage.setItem('settings', JSON.stringify(userSettings));
// 2. 读取:先获取字符串,再用 JSON.parse() 转换回对象
const storedSettingsStr = localStorage.getItem('settings');
const retrievedSettings = JSON.parse(storedSettingsStr);
console.log(retrievedSettings.theme); // "dark"
// 3. 移除
localStorage.removeItem('settings');
// 4. 清空所有
localStorage.clear();

⚡ 二、会话存储(Session Storage):临时的“标签页便签”

Session Storage 的 API 与 Local Storage 完全相同,但其生命周期截然不同。它的数据仅在当前浏览器标签页的会话期间有效。

核心特征:

  • 生命周期会话级别。一旦标签页或浏览器被关闭,存储的数据就会被清除。
  • 作用域标签页隔离。即使是同源的页面,在不同的标签页中打开,它们的 Session Storage 也是相互独立的。
  • 数据格式与容量:与 Local Storage 相同(字符串,约 5MB)。

适用场景:

  • 单次会话的临时数据:如多步骤表单中前几步填写的信息,用户关闭页面后就无需保留。
  • 防止页面刷新导致数据丢失:在单页面应用(SPA)中,可以将当前页面的状态(如滚动位置、选项卡)存入 Session Storage,刷新后可以恢复。
  • 临时存储敏感信息:比如一次性登录的 token,关闭标签页后自动失效,比 Local Storage 更安全。

优点:

  • 数据隔离性好,不会在不同标签页间造成数据污染。
  • 生命周期由浏览器自动管理,用完即焚,无需手动清理。

缺点:

  • 应用范围受限,无法实现跨标签页的数据共享。

代码示例:

// 在一个多步骤表单的第一页
const step1Data = { name: 'Alice', email: '[email protected]' };
sessionStorage.setItem('formStep1', JSON.stringify(step1Data));
// 在第二页,可以读取第一页的数据
const previousData = JSON.parse(sessionStorage.getItem('formStep1'));
if (previousData) {
console.log(`Welcome, ${previousData.name}!`);
}
// 当用户关闭这个标签页时,'formStep1' 会被自动删除。

🍪 三、Cookie:服务器与客户端的“通行证”

Cookie 是历史最悠久的浏览器存储机制。它的核心设计目标是在无状态的 HTTP 协议上维持状态,因此它最大的特点是会自动在客户端与服务器之间双向传递

Cookie 是一小段文本信息,由服务器通过 Set-Cookie HTTP 响应头发送给浏览器。浏览器保存后,在后续向该服务器发起的每一个请求中,都会自动通过 Cookie HTTP 请求头将这些信息带回给服务器。

核心交互流程

  1. 服务器设置:客户端首次请求后,服务器在响应头中加入 Set-Cookie

    HTTP/1.1 200 OK
    Content-Type: text/html
    Set-Cookie: sessionId=a3fWa; HttpOnly; SameSite=Lax
  2. 浏览器存储:浏览器收到响应,将 sessionId=a3fWa 这条 Cookie 与该域名关联并保存。

  3. 浏览器自动发送:当用户再次访问该域名下的任何资源(页面、API、图片等),浏览器都会在请求头中自动添加 Cookie

    GET /api/user/profile HTTP/1.1
    Host: example.com
    Cookie: sessionId=a3fWa
  4. 服务器读取与识别:服务器通过读取请求头中的 Cookie,识别出用户身份,从而提供个性化的响应。

Cookie 不只是键值对,其行为由一系列属性精确控制。

属性作用示例
Expires / Max-Age控制生命周期Expires 是一个绝对的过期时间点,Max-Age 是相对的过期秒数(更推荐)。若不设置,则为会话 Cookie,浏览器关闭时删除。Max-Age=3600 (1 小时后过期)
Domain控制生效域名。可设置为父域名(如 .example.com),使其在所有子域名(a.example.com, b.example.com)中共享。Domain=.example.com
Path控制生效路径。只有当请求的路径匹配时,才会发送 Cookie。通常设置为 /,表示整个域名下都有效。Path=/
HttpOnly核心安全属性。设置后,该 Cookie 无法通过 JavaScript (document.cookie) 访问,能有效防御 XSS 攻击窃取 Cookie。HttpOnly
Secure核心安全属性。设置后,该 Cookie 只会在 HTTPS 连接中被发送,防止在不安全的 HTTP 连接中被中间人窃听。Secure
SameSite核心安全属性。用于防御 CSRF 攻击,控制在跨站请求中是否发送 Cookie。
- Strict: 最严格,完全禁止跨站发送。
- Lax: (现代浏览器默认值) 允许部分安全的顶层导航(如链接跳转)发送,但禁止在 <img><iframe>POST 表单等场景下发送。
- None: 允许所有跨站请求发送,但必须同时设置 Secure 属性
SameSite=Strict

前端通过 document.cookie 操作非 HttpOnly 的 Cookie,但其 API 非常原始和不便。

  • 读取document.cookie 返回一个由 ; 分隔的所有 Cookie 的字符串,需要手动解析。
  • 写入/更新:每次赋值都是新增或覆盖一个 Cookie,而不是替换整个字符串。
// 直接操作 document.cookie(不推荐)
document.cookie = "username=Alice; max-age=3600; path=/; SameSite=Lax";

推荐方案:使用成熟的库(如 js-cookie)或封装辅助函数来简化操作。

// 简单的辅助函数示例
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
特性会话 Cookie持久 Cookie
存储位置浏览器内存设备硬盘
生命周期浏览器关闭即删除持续到 ExpiresMax-Age 到期
设置标志不包含 Expires/Max-Age必须包含 ExpiresMax-Age
安全性较高(临时存在)需额外保护(加密 + HttpOnly/Secure
典型用途临时会话、购物车、单次登录状态持久登录、用户偏好、长期跟踪
用户控制无法手动删除(关闭浏览器自动清除)可通过浏览器设置或开发者工具手动删除

5. 总结:Cookie 的优缺点

优点缺点
自动发送,服务器驱动:无需手动管理,是实现有状态会话的基石。性能开销:每个同源请求(包括图片、CSS)都会携带,增加了请求头的大小。
强大的控制属性:通过 HttpOnly, Secure, SameSite 等属性可实现高安全性。容量极小:仅约 4KB,不适合存储复杂数据。
兼容性极佳:所有浏览器都支持。API 简陋:前端原生 API 难以使用,需要封装或使用库。
跨子域名共享:通过设置 Domain 属性,轻松实现主域名与子域名间的通信。安全风险:配置不当容易遭受 XSS 和 CSRF 攻击。

🗂️ 四、IndexedDB:前端的强大数据库

当需要存储大量结构化数据,并进行高效查询时,IndexedDB 是不二之选。它是一个内置在浏览器中的事务型、异步的 NoSQL 数据库

核心概念:

  • 数据库(Database):存储的顶层容器,按域名划分。
  • 对象存储空间(Object Store):类似于 SQL 中的表,用于存储数据对象。
  • 索引(Index):用于对 Object Store 中的数据按特定属性进行快速检索。
  • 事务(Transaction):所有数据操作(增删改查)都必须在事务中进行,保证了操作的原子性(要么全部成功,要么全部失败)。
  • 异步 API:所有操作都是异步的,通过事件回调或 Promise 来处理结果,不会阻塞主线程。

优点:

  • 存储容量巨大:通常可达数百 MB 甚至数 GB,具体取决于用户的磁盘空间。
  • 支持复杂数据类型:可以存储 JavaScript 对象、文件、Blob 等。
  • 支持事务:保证了数据的一致性和可靠性。
  • 支持索引查询:可以根据数据的任意属性建立索引,实现快速检索。
  • 可用于 Web Workers:可以在后台线程中操作数据库,避免影响 UI 性能。

缺点:

  • API 相对复杂:原生 API 基于事件回调,比较繁琐。强烈推荐使用封装库,如 dexie.js,它提供了非常友好的 Promise-based API。
  • 不适合服务器通信:它纯粹是客户端数据库,数据不会自动发送到服务器。

代码示例(使用 dexie.js 简化):

// 引入 dexie.js
// <script src="https://unpkg.com/dexie/dist/dexie.js"></script>
// 1. 定义数据库结构
const db = new Dexie('MyFriendsDB');
db.version(1).stores({
friends: '++id, name, age' // '++id' 表示自增主键,'name' 和 'age' 是索引
});
// 2. 添加数据
async function addFriend() {
try {
await db.friends.add({
name: 'Bob',
age: 30,
});
console.log('Friend added!');
} catch (error) {
console.error('Failed to add friend:', error);
}
}
// 3. 查询数据
async function findFriends() {
// 根据索引 'age' 查询
const oldFriends = await db.friends.where('age').above(25).toArray();
console.log('Friends older than 25:', oldFriends);
}
addFriend();
findFriends();

💾 五、Cache Storage:为离线体验而生

Cache Storage 是一个专门用于存储 HTTP 请求和响应的缓存机制。它通常与 Service Worker 结合使用,是实现**渐进式网络应用(PWA)**离线访问能力的核心技术。

工作流程(与 Service Worker 配合):

  1. 拦截请求:Service Worker 作为一个网络代理,可以拦截页面发出的所有网络请求。
  2. 查询缓存:对于被拦截的请求,Service Worker 会首先检查 Cache Storage 中是否存在匹配的、已缓存的响应。
  3. 响应策略
    • 缓存命中(Cache Hit):如果找到缓存,则直接将缓存的响应返回给页面,无需发起网络请求,速度极快,且在离线时也能工作。
    • 缓存未命中(Cache Miss):如果未找到缓存,则将请求转发到网络。获取到网络响应后,一方面将其返回给页面,另一方面可以将其副本存入 Cache Storage,以便下次使用。

优点:

  • 实现真正的离线访问:可以缓存整个应用的 Shell(HTML、CSS、JS)和数据。
  • 提升加载速度:对于已缓存的资源,加载速度接近瞬时。
  • 编程控制力强:开发者可以完全控制缓存的策略(何时缓存、缓存什么、何时更新)。
  • 存储容量大:与 IndexedDB 类似,容量非常可观。

示例(在 Service Worker 中):

service-worker.js
const CACHE_NAME = 'my-app-cache-v1';
const urlsToCache = ['/', '/styles/main.css', '/script/main.js'];
// 1. 安装时缓存核心资源
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
// 2. 拦截 fetch 请求并应用缓存策略
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// 如果缓存中有,则直接返回缓存的响应
if (response) {
return response;
}
// 否则,发起网络请求
return fetch(event.request);
})
);
});

📦 六、Web SQL(已废弃)

  • 特征:一个基于 SQL 的关系型数据库 API。
  • 状态已被 W3C 废弃。由于缺乏统一的 SQL 方言标准,主流浏览器已停止支持。
  • 建议绝对不要在新的项目中使用。对于需要结构化存储的场景,请使用 IndexedDB

✅ 总结对比表

存储方式容量生命周期与服务器通信API 复杂度主要用途
Cookie~4KB可自定义过期时间自动双向传递原始 API 复杂身份认证、会话保持、跟踪用户行为
Local Storage~5MB永久(手动清除)需手动通过代码发送非常简单用户偏好、持久化 UI 状态、非关键数据缓存
Session Storage~5MB标签页会话级需手动通过代码发送非常简单多步表单临时数据、单页应用临时状态
IndexedDB大型(GB 级)永久(手动清除)需手动通过代码发送原生 API 复杂客户端数据库、大量结构化数据、离线应用数据
Cache Storage大型(GB 级)永久(代码控制)存储网络请求/响应中等(常与 SW 结合)PWA 离线缓存、应用 Shell 缓存、提升性能