# localStorage 与 sessionStorage

> localStorage 与 sessionStorage 的区别、同源与按源配额规则，以及隐私浏览模式下的行为为何因浏览器而异。

**一句话：** `localStorage` 与 `sessionStorage` 都是暴露在 `Window` 上的同步、仅存字符串、
同源的键值存储；`localStorage` 会跨标签页和浏览器重启持久保存，而 `sessionStorage` 只作用于
单个标签页，在该标签页关闭时被清除。

## 读写数据

```js
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // 移除该源下的所有键

// sessionStorage 拥有完全相同的方法集，作用范围为当前标签页：
sessionStorage.setItem("draftId", "42");
```

根据 MDN 的使用指南，两个接口都只存储字符串——对象或数组等非字符串数据必须显式转换，
例如使用 `JSON.stringify()`。

## 浏览器支持与配额

`localStorage` 与 `sessionStorage` 都是 Web Storage API 中历史悠久、被广泛实现的部分，
在当前主流浏览器中均可用。根据 MDN，二者是各自独立的 `Storage` 对象，"function and are
controlled separately"（各自独立运作和控制）。根据 MDN 的使用指南，当该源可用的存储空间
全部用尽后，写入仍可能失败，此时会抛出 `QuotaExceededError` 这一 `DOMException`，而不是
静默失败。

## 响应其他标签页的变化

```js
window.addEventListener("storage", (event) => {
  console.log(event.key, event.oldValue, event.newValue);
});
```

`storage` 事件会在 `localStorage` 发生变化时，触发在共享同一存储区域的**其他** `Document`
上——绝不会触发在发起变更的那个 document 自身。根据 MDN，这意味着同一标签页中其他同源的
document（例如一个 iframe）仍可能收到该事件，即使发起写入的 document 不会。可用它来保持
其他已打开的 document 之间的同步。

## 如何在运行时检测

```js
function storageAvailable() {
  if (!('localStorage' in window)) {
    // localStorage 属性不存在 —— 回退到本次会话的内存存储。
    return false;
  }
  try {
    const probe = "__storage_test__";
    window.localStorage.setItem(probe, probe);
    window.localStorage.removeItem(probe);
    return true;
  } catch {
    // 接口存在但不可用——例如某些隐私浏览模式会报告 0 字节配额——因此视为
    // 不可用，并采用相同的回退方式。
    return false;
  }
}
```

这与 MDN 自身在"Using the Web Storage API"中给出的特性检测方法类似，同样通过一次真实
写入来探测——不过 MDN 的版本会把已有数据的存储上抛出的 `QuotaExceededError` 视为存储
仍然可用的证据，而不是像上面这个简化探测那样，一旦捕获异常就无条件判定为不可用。

## 常见问题

- [ ] 值始终是字符串——存入结构化数据前先用 `JSON.stringify()`，取出后用
      `JSON.parse()`。
- [ ] 两者都是**同源**的：协议、主机、端口必须全部一致，因此 `https://example.com`
      与 `http://example.com` 不共享存储。
- [ ] `storage` 事件只会在共享存储区域的*其他* document 上触发，绝不会在发起写入的那个
      document 上触发——不要在本地等待它。根据 MDN，同一标签页中其他同源的 document
      （例如一个 iframe）仍可能收到该事件。
- [ ] 隐私/无痕模式下的行为在各浏览器间并不一致。根据 MDN 的 Web Storage 指南，
      在隐私窗口中写入的数据通常会被当作 `sessionStorage` 处理，在最后一个隐私
      标签页关闭时被清除——但 MDN 同时指出，部分浏览器会改为暴露一个配额固定为
      **0 字节**的 `localStorage` 对象，写入会立即抛出，而不是保留到标签页关闭。
      应通过真实的写入/删除探测来做特性检测，而不是假设某一种行为。
- [ ] 所有读写都是**同步**的，并运行在主线程上；较大的值或频繁写入会阻塞渲染——
      对于体量较大的数据应优先使用 IndexedDB。
- [ ] 根据 MDN，`sessionStorage` 是按源与顶层浏览上下文共同划分的——并不只是简单地
      "每个标签页一份"。根据 MDN，一个持有 `opener`（指向打开它的那个页面）引用的
      新页面，在初始时会获得该 opener 的 `sessionStorage` 副本；没有 opener 的
      上下文则不会。

## 下一步

- [存储持久化、配额与驱逐](/zh/reference/storage/persistence/) —— 与源存储配额相关
  的参考条目。
- [清除站点数据](/zh/reference/storage/clearing-data/) —— 与清除站点存储相关的
  参考条目。