5MB vs 4KB vs 无限大:浏览器存储谁更强?

27 阅读11分钟

你有没有想过这个问题:为什么在网页上勾选了"记住我",下次打开还是登录状态?你改了个主题设置,关掉浏览器再打开,主题还在?浏览器是怎么记住这些数据的?

今天,用**"收纳房间"**的故事,来讲讲浏览器存储。


原文地址

墨渊书肆/5MB vs 4KB vs 无限大:浏览器存储谁更强?


浏览器是怎么"装东西"的?

想象一下你家要装修,需要各种收纳工具:

  • 贴身口袋:装点小东西,随时能用
  • 床头柜:装常用物品,随取随用
  • 衣柜:装换季衣服,大容量
  • 仓库:存大件物品,最大但找起来麻烦

浏览器存储也是这个道理。不同的数据,要用不同的"收纳工具"。


Cookie — 贴身口袋

像个口袋,随身带

Cookie 是最"古老"的浏览器存储方案。它最大的特点是——会自动跟着请求一起发出去

就像你出门带了个口袋,里面装着身份证、银行卡。进任何一家店,都要掏出身份证证明身份。

浏览器也是:每次请求网页,Cookie 都自动带上,服务器就知道"哦,这是张三的浏览器"。

Cookie 的特点

属性像什么
容量~4KB口袋里只能装这么多
发送自动随请求发送出门就带
生命周期可设置过期时间可以设有效期
访问JS和服务器都能读谁都能用

Cookie 的使用场景

  • 登录状态:"记住我"功能
  • 购物车:逛淘宝加购物车
  • 追踪分析:埋点上报

Cookie 的代码

// 设置Cookie
document.cookie = "username=张三; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/";

// 读取Cookie
console.log(document.cookie);  // "username=张三; theme=dark"

Cookie 的安全问题

Cookie 虽然方便,但有几个安全属性要注意:

属性作用什么意思
HttpOnlyJS无法访问口袋上锁了,店员碰不到
Secure只在HTTPS发送只能用加密通道
SameSite防止CSRF攻击别人拿不到你的卡

深入了解 Cookie 🔬

Cookie 是怎么工作的?

Cookie 由 HTTP 协议定义,通过 Set-Cookie 响应头设置:

HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure; SameSite=Strict

HTTP/1.1 200 OK
Cookie: sessionId=abc123

浏览器怎么存 Cookie?

每个浏览器都有自己的存储方式:

浏览器存储位置
Chrome/EdgeSQLite 数据库 (%APPDATA%\Local\Google\Chrome\User Data\Default\Cookies)
FirefoxJSON 文件 (cookies.sqlite)
Safari二进制文件

Cookie 的发送规则?

浏览器根据 Domain + Path + SameSite 三个规则决定是否发送:

// 例如:Cookie 设置为 Domain=example.com, Path=/admin
// 会发送给:
// ✅ example.com/admin
// ✅ example.com/admin/users
// ❌ example.com/ (path不匹配)
// ❌ other.com/admin (domain不匹配)

Session Cookie vs 持久 Cookie?

# 会话Cookie(没有Expires/Max-Age)
Set-Cookie: sessionId=abc123
# 关掉浏览器就失效

# 持久Cookie
Set-Cookie: sessionId=abc123; Expires=Wed, 01 Jan 2027 00:00:00 GMT
# 有效期内都有效

LocalStorage — 床头柜

容量大,但不主动发

LocalStorage 是 HTML5 引入的存储方案。最大的特点:不会随请求发出去

就像床头柜——你把东西放里面,下次进门直接拿,不用每次出门都背着。

LocalStorage 的特点

属性像什么
容量~5MB/域床头柜大小
发送不随请求发送不随身带
生命周期永久存储除非搬家(手动删除)
API同步操作马上拿到

LocalStorage 的使用场景

  • 主题设置:深色/浅色模式
  • 用户偏好:字体大小、语言设置
  • 数据缓存:接口数据本地缓存

LocalStorage 的代码

// 设置
localStorage.setItem('username', '张三');
localStorage.setItem('theme', 'dark');

// 读取
const theme = localStorage.getItem('theme');  // 'dark'

// 删除
localStorage.removeItem('theme');

// 清空
localStorage.clear();

// 遍历
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(`${key}: ${localStorage.getItem(key)}`);
}

LocalStorage 的缺点

  • 同步操作:大量数据会卡界面
  • 只能存字符串:对象要转成 JSON
  • 容量有限:5MB 对大数据不够

深入了解 LocalStorage 🔬

同源策略限制

LocalStorage 遵循同源策略:

✅ http://example.com 和 https://example.com 共享同一个Storage
✅ http://example.com:8080 和 http://example.com:3000 不共享(端口不同)
✅ http://www.example.com 和 http://example.com 不共享(子域名不同)

存储配额

实际容量取决于浏览器和磁盘空间,Chrome 默认是 5MB(可申请更多):

// 查询当前配额和使用量
navigator.storage.estimate().then(({ usage, quota }) => {
  console.log(`已使用: ${(usage / 1024 / 1024).toFixed(2)} MB`);
  console.log(`总配额: ${(quota / 1024 / 1024).toFixed(2)} MB`);
});

// 请求更大的存储空间(需要用户授权)
navigator.storage.persist().then((granted) => {
  console.log('永久存储权限:', granted);
});

为什么 LocalStorage 是同步的?

因为 LocalStorage 读取是直接读磁盘。如果数据量大,同步读取会阻塞主线程:

// ❌ 错误:大数据量时卡界面
localStorage.setItem('bigData', JSON.stringify(largeArray));

// ✅ 更好:拆分存储或用 IndexedDB

SessionStorage — 抽屉

只在当前标签页有效

SessionStorageLocalStorage 几乎一样,唯一的区别是——关闭标签页就没了

就像抽屉里的东西,只有在这个房间能用。换到另一个房间(另一个标签页),抽屉里的东西就不在了。

SessionStorage 的特点

属性和LocalStorage的区别
容量~5MB/域一样
作用域仅当前标签页❌ 跨标签页不共享
生命周期关闭标签页失效❌ 不能持久保存

SessionStorage 的使用场景

  • 表单草稿:填写到一半的表单
  • 临时状态:当前页面的操作状态

SessionStorage 的代码

// 用法和LocalStorage完全一样
sessionStorage.setItem('draft', JSON.stringify({ title: '我的文章', content: '...' }));

关键区别

// 标签页A中设置
sessionStorage.setItem('key', 'value');
localStorage.setItem('key', 'value');

// 在标签页B中读取
sessionStorage.getItem('key');  // null ❌
localStorage.getItem('key');    // 'value' ✅

深入了解 SessionStorage 🔬

iframe 共享问题

注意:同一个标签页中的 iframe 会共享 SessionStorage(因为是同一个浏览器标签页):

// 父页面
sessionStorage.setItem('shared', 'value');

// iframe 内可以读取到
console.log(sessionStorage.getItem('shared'));  // 'value'

sessionStorage 在隐私模式下

  • Chrome 无痕模式sessionStorage 仍然存在,但标签页关闭后失效
  • Firefox 隐私窗口:完全隔离,每个新窗口都是新的 sessionStorage

和 LocalStorage 的性能对比

两者都是同步 API,性能特性相同。但 SessionStorage 因为数据不持久,有时候比 LocalStorage 更适合存临时数据。


IndexedDB — 仓库

浏览器里的数据库

IndexedDB 是浏览器内置的数据库。容量巨大,能存文件、音频、视频这些大东西。

就像仓库——你家装修工具、电风扇、行李箱都放这儿。东西多,但找起来要翻半天。

IndexedDB 的特点

属性像什么
容量很大(取决于磁盘)仓库,接近无限
数据类型什么都能存不挑东西
API异步操作异步,不卡界面
查询支持索引能分类查找

IndexedDB 的使用场景

  • 离线数据:PWA离线应用
  • 多媒体存储:图片、音频、视频缓存
  • 复杂数据:需要索引查询的数据

IndexedDB 的代码

// 打开数据库
const request = indexedDB.open('myDatabase', 1);

// 创建表(对象存储)
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const store = db.createObjectStore('users', { keyPath: 'id' });
  store.createIndex('name', 'name', { unique: false });
  store.createIndex('email', 'email', { unique: true });
};

// 添加数据
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction(['users'], 'readwrite');
  const store = tx.objectStore('users');

  store.add({ id: 1, name: '张三', email: 'zhangsan@example.com' });
  store.add({ id: 2, name: '李四', email: 'lisi@example.com' });
};

// 查询数据
const getRequest = store.get(1);
getRequest.onsuccess = () => {
  console.log('查询结果:', getRequest.result);
};

// 使用索引查询
const index = store.index('name');
const indexRequest = index.get('张三');
indexRequest.onsuccess = () => {
  console.log('索引查询结果:', indexRequest.result);
};

IndexedDB 的缺点

  • API 复杂:需要写一堆回调
  • 学习成本高:概念多(数据库、表、事务、索引)

深入了解 IndexedDB 🔬

数据库版本和升级

const request = indexedDB.open('myDatabase', 2);  // 版本号从1升到2

request.onupgradeneeded = (event) => {
  const db = event.target.result;

  // 创建新存储
  if (!db.objectStoreNames.contains('products')) {
    db.createObjectStore('products', { keyPath: 'id' });
  }

  // 删除旧存储
  if (db.objectStoreNames.contains('oldData')) {
    db.deleteObjectStore('oldData');
  }
};

事务的原子性

const tx = db.transaction(['users', 'orders'], 'readwrite');

// 两个操作在一个事务里,要么全成功,要么全失败
tx.objectStore('users').add({ id: 1, name: '张三' });
tx.objectStore('orders').add({ id: 1, userId: 1, product: '电脑' });

tx.oncomplete = () => console.log('事务成功');
tx.onerror = () => console.log('事务失败,全部回滚');

游标遍历大量数据

const tx = db.transaction(['users'], 'readonly');
const store = tx.objectStore('users');
const cursor = store.openCursor();

cursor.onsuccess = (event) => {
  const cur = event.target.result;
  if (cur) {
    console.log('用户:', cur.value.name);
    cur.continue();  // 继续下一个
  }
};

Promise 封装(更简洁的写法)

function openDB(name, version) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(name, version);
    request.onupgradeneeded = (e) => resolve(e.target.result);
    request.onsuccess = (e) => resolve(e.target.result);
    request.onerror = (e) => reject(e.target.error);
  });
}

// 使用
const db = await openDB('myDatabase', 1);
const tx = db.transaction('users', 'readwrite');
await tx.objectStore('users').add({ id: 1, name: '张三' });

Cache API — 集装箱

Service Worker 的专属工具

Cache API 是 Service Worker 的一部分,专门用来缓存网络请求。

就像集装箱——你坐飞机带不了大件行李,但可以用集装箱海运。东西多、个头大,但只能走特定渠道。

Cache API 的特点

属性像什么
容量很大集装箱,装得多
存储内容Request/Response 对整套打包
生命周期手动管理不用就扔
API异步操作不卡界面

Cache API 的使用场景

  • 离线应用:把整个网站缓存下来
  • 性能优化:缓存静态资源
  • Service Worker:配合SW实现缓存策略

Cache API 的代码

// 在Service Worker中使用
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((cachedResponse) => {
        return cachedResponse || fetch(event.request);
      })
  );
});

// 打开缓存
caches.open('my-cache').then((cache) => {
  cache.addAll([
    '/css/style.css',
    '/js/app.js',
    '/images/logo.png'
  ]);
});

// 缓存特定请求
cache.put(request, response);

// 删除缓存
caches.delete('my-cache');

深入了解 Cache API 🔬

缓存策略

// Cache First(缓存优先)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request)
      .then((response) => response || fetch(event.request))
  );
});

// Network First(网络优先)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .catch(() => caches.match(event.request))
  );
});

// Stale-While-Revalidate(先返回缓存,同时更新缓存)
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open('my-cache').then((cache) => {
      return cache.match(event.request).then((response) => {
        const fetchPromise = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    })
  );
});

Cache API 和 cookies

Cache API 存储的是完整的 Request/Response 对,不只是 body:

// 缓存时包含了headers、status等所有信息
cache.match(request).then((response) => {
  console.log(response.status);      // 200
  console.log(response.headers.get('content-type'));  // 'text/html'
});

缓存清理策略

// 删除指定缓存
caches.delete('old-cache');

// 清理所有版本,只保留最新的
caches.keys().then((cacheNames) => {
  Promise.all(
    cacheNames
      .filter((name) => name.startsWith('app-') && name !== 'app-v2')
      .map((name) => caches.delete(name))
  );
});

Storage Event — 跨标签页喊话

标签页之间能"喊话"

当 LocalStorage 发生变化时,其他同源的标签页会收到通知。

就像你在客厅喊了一句"饭好了",厨房的人、卧室的人都能听到。

Storage Event 的代码

// 标签页A中监听
window.addEventListener('storage', (event) => {
  console.log('key:', event.key);      // 变化的键
  console.log('oldValue:', event.oldValue);  // 旧值
  console.log('newValue:', event.newValue);  // 新值
  console.log('url:', event.url);      // 触发变化的页面URL
  console.log('storageArea:', event.storageArea);  // localStorage 或 sessionStorage
});

// 标签页B中修改
localStorage.setItem('theme', 'dark');  // 标签页A会收到通知

使用场景

  • 多标签页同步:一个标签页登录,其他标签页同步登录状态
  • 状态广播:跨标签页的状态通知

深入了解 Storage Event 🔬

Storage Event 的触发条件

// ✅ 会触发 storage 事件
localStorage.setItem('key', 'value');
localStorage.removeItem('key');
localStorage.clear();

// ❌ 不会触发 storage 事件(同一个标签页)
// Storage Event 只在「其他标签页」变化时触发

SessionStorage 也会触发?

注意:SessionStorage 本身不跨标签页共享,但 Storage Event 只监听 localStorage 的变化。

// SessionStorage 变化不会触发 storage 事件
sessionStorage.setItem('key', 'value');  // 不会触发其他标签页

// localStorage 变化会触发
localStorage.setItem('key', 'value');  // 其他标签页会收到通知

隐私模式下不触发

在无痕/隐私模式下,Storage Event 不会触发,这是浏览器的隐私保护机制。


横向对比

特性CookieLocalStorageSessionStorageIndexedDBCache API
容量~4KB~5MB~5MB很大很大
生命周期可设置永久关闭失效永久手动
发送自动发不发不发不发不发
API简单同步简单同步简单异步复杂异步
数据类型字符串字符串字符串所有可序列化Request/Response
跨标签页共享共享不共享共享不共享

怎么选?

场景推荐
需要服务器读取Cookie
存用户偏好、主题LocalStorage
临时状态、标签页隔离SessionStorage
大数据、离线存储IndexedDB
Service Worker缓存Cache API

注意事项

1. 不要存敏感信息

LocalStorage 可以被 JS 访问,XSS 攻击能偷走数据。敏感信息用 HttpOnly Cookie。

2. 存储配额

浏览器对存储有限制,可以用 API 查询:

navigator.storage.estimate().then(({ usage, quota }) => {
  console.log('已用:', (usage / 1024 / 1024).toFixed(2), 'MB');
  console.log('总配额:', (quota / 1024 / 1024).toFixed(2), 'MB');
});

3. 序列化问题

LocalStorage 和 SessionStorage 只能存字符串,对象要转 JSON:

// 存
localStorage.setItem('data', JSON.stringify({ name: '张三' }));

// 取
const data = JSON.parse(localStorage.getItem('data'));

4. 同步 API 的性能问题

LocalStorage/SessionStorage 是同步操作,大量数据会阻塞主线程:

// ❌ 不好:大量数据卡界面
for (let i = 0; i < 10000; i++) {
  localStorage.setItem(`key${i}`, `value${i}`);
}

// ✅ 更好:用 IndexedDB 存储大量数据

总结

存储方式像什么特点
Cookie口袋小、随请求发、安全属性多
LocalStorage床头柜5MB、不发送、永久
SessionStorage抽屉5MB、不发送、仅标签页
IndexedDB仓库巨大、异步、复杂
Cache API集装箱Service Worker专用

选对"收纳工具",数据管理更轻松。


写在最后

现在你应该明白了:

  • Cookie = 口袋,随身带、自动发送、容量小
  • LocalStorage = 床头柜,大容量、不发送、永久保存
  • SessionStorage = 抽屉,只在当前标签页有效
  • IndexedDB = 仓库,最大但操作复杂
  • Cache API = 集装箱,Service Worker专用

下次你在网页上勾选"记住我",或者调整了主题设置——你就知道浏览器是用哪种"收纳工具"帮你存的了。