# 存储配额、持久化与逐出

> 对照 Chromium、Firefox 与 WebKit 的存储上限、持久化模式和逐出规则，并给出运行时检查与恢复分支。

存储配额限制来源能写入多少数据；持久化主要决定存储桶能否由浏览器自动清理；逐出则是浏览器按清理策略删除数据。配额仍由浏览器实现决定，并可把 `persistent-storage` 权限纳入计算，因此 `persist()` 成功后来源配额也可能改变。例如，Firefox 会对获准持久化的来源应用更高上限。

页面兼容表读取 [Persistent Storage](/zh/compatibility/persistent-storage/) 中 `api.StorageManager.persist` 的数据。

## 工作原理

Storage Standard 为每个 storage shelf 设置默认存储桶。本地存储桶的模式可为 `best-effort` 或 `persistent`。规范把 usage 定义为由实现决定的粗略估值，把 quota 定义为由实现决定的保守估值。浏览器还会使用压缩、去重和隐私保护，因此 `estimate()` 返回的数值不是磁盘占用的精确读数（[Storage Standard](https://storage.spec.whatwg.org/#usage-and-quota)，storage.spec.whatwg.org）。

持久化可保护本地存储桶，使用户代理不能在来源或用户未参与的情况下清除它；用户仍可主动删除这些数据。`persist()` 请求 `persistent-storage` 权限，并以布尔值报告默认存储桶是否已经持久化。`persisted()` 只读取模式，不请求变更（[Storage Standard](https://storage.spec.whatwg.org/#persistence)，storage.spec.whatwg.org）。

逐出针对浏览器按来源管理的存储，包括 IndexedDB、Cache Storage 和 Origin Private File System。MDN 记载，浏览器逐出某来源时会一并删除其受管理的数据，不会只裁掉某个数据库。Storage Standard 同样要求清理整个存储桶（[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)，developer.mozilla.org；[Storage Standard](https://storage.spec.whatwg.org/#management)，storage.spec.whatwg.org）。

| 决策 | 要回答的问题 | 浏览器信号 | 应用处理 |
|---|---|---|---|
| 配额 | 下一次写入能否容纳？ | `navigator.storage.estimate()` 给出近似的 `usage` 与 `quota`。 | 捕获写入错误，提供清理或服务端恢复。 |
| 持久化 | 浏览器能否自动清理这个存储桶？ | `persisted()` 报告模式，`persist()` 请求变更。 | 在结果为 `true` 前，关键的本地数据仍须可恢复。 |
| 逐出 | 浏览器需要空间时会怎样？ | Storage Standard 没有定义逐出事件。 | 启动时检查必要记录和缓存，重建缺失数据。 |

### Chromium 策略

Chrome 的存储指南写明：Chrome 可让浏览器使用总磁盘空间的 80%，单个来源可使用 60%。空间不足时，浏览器按最近最少使用顺序清除 best-effort 来源。其他 Chromium 浏览器的策略可能不同（[Storage for the web](https://web.dev/articles/storage-for-the-web)，web.dev）。

Chrome 等 Chromium 浏览器通常根据站点互动情况自动批准或拒绝持久化请求，不显示权限提示。返回 `false` 只表示存储桶仍是 best-effort 模式，不代表刚才的写入已经失败（[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)，developer.mozilla.org）。

### Firefox 策略

MDN 记载，Firefox 的 best-effort 上限取两者中较小值：用户配置所在磁盘总容量的 10%，或同一站点下各来源共享的 10 GiB 分组上限。获准持久化的来源最多可用总磁盘空间的 50%，上限 8 TiB，且不受分组上限约束。Firefox 会在站点请求持久化时询问用户；发生存储压力时，它跳过持久化来源，按最近最少使用顺序逐出 best-effort 来源（[Storage quotas and eviction criteria](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria)，developer.mozilla.org）。

配额按磁盘总容量计算，不按可用空间计算。因此，估值可能大于设备实际还能写入的空间。即使估值看起来充足，代码仍须处理写入失败。

### WebKit 策略

WebKit 分别规定来源配额和总体配额。从 Safari 17.0，以及 iOS 17、iPadOS 17 和 macOS Sonoma 的 WebKit App 开始，浏览器 App 中单个来源最多可用总磁盘空间的 60%，总体配额最多为 80%；嵌入网页内容的其他 App 上限更低。iOS 主屏 Web App 或 macOS Dock Web App 与浏览器 App 中的同站点使用相同的来源配额和总体配额（[Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)，webkit.org）。

WebKit 列出的数据删除路径包括超过总体配额、系统存储压力和 Intelligent Tracking Prevention。ITP 文档记载，从 iOS、iPadOS 13.4 和 macOS Safari 13.1 起，站点连续七个 Safari 使用日没有用户互动后，其脚本可写存储会被删除；主屏 Web App 单独计算使用日（[Full Third-Party Cookie Blocking and More](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/)，webkit.org）。

:::caution
达到来源配额与数据遭到逐出是两条不同的失败路径。WebKit 说明，写入所需空间超过来源配额时会抛出 `QuotaExceededError`；达到总体配额则可能触发逐出。写入处要捕获 `QuotaExceededError`，后续启动时也要检查存储状态（[Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)，webkit.org）。
:::

## 示例

第一个示例读取模式与估值，但不会把任一结果当作后续写入成功的保证。

### 检查配额与持久化模式

```js
async function readStorageState() {
  if (!navigator.storage?.estimate) {
    return { supported: false, persisted: false };
  }

  const { usage = 0, quota = 0 } = await navigator.storage.estimate();
  const persisted = navigator.storage.persisted
    ? await navigator.storage.persisted()
    : false;

  return { supported: true, usage, quota, persisted };
}

readStorageState().then((state) => console.log(state));
```

不支持时，界面可以根据回退结果显示“不可用”。支持时，`usage` 与 `quota` 仍是以字节表示的估值，应用不应据此计算下一笔事务的保证容量。

第二个示例分别处理持久化结果和配额错误。若本地存储无法接受草稿，它会把内容保存到服务端。

### 写入超过配额时恢复

```js
async function saveDraft(draft) {
  const persistent = navigator.storage?.persist
    ? await navigator.storage.persist()
    : false;

  try {
    await draftsDatabase.put(draft);
    return { saved: 'local', persistent };
  } catch (error) {
    if (error?.name !== 'QuotaExceededError') throw error;
    await fetch('/api/drafts', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(draft),
    });
    return { saved: 'server', persistent };
  }
}
```

持久化结果与写入结果互不替代。`persist()` 返回 `false` 时，写入仍可能成功；`QuotaExceededError` 则进入 Chrome 与 WebKit 存储指南所述的显式恢复分支。

下次启动时，应检查应用所需的具体记录，不要只凭 `estimate()` 推断是否发生逐出。数据库缺失也可能源于首次访问或用户主动清理。[逐出与尽力而为存储](/zh/reference/storage/eviction/) 说明恢复边界，[Safari 对脚本可写存储的 7 天上限](/zh/reference/storage/safari-seven-day-cap/) 说明 WebKit 的主动删除路径。

## 另请参阅

- [Storage Standard](https://storage.spec.whatwg.org/)（storage.spec.whatwg.org）
- [Updates to Storage Policy](https://webkit.org/blog/14403/updates-to-storage-policy/)（webkit.org）
- [Storage for the web](https://web.dev/articles/storage-for-the-web)（web.dev）
- [StorageManager.persist() 与 persisted()](/zh/reference/storage/persistence/)
- [StorageManager.estimate()](/zh/reference/storage/quota-estimate/)
- [逐出与尽力而为存储](/zh/reference/storage/eviction/)
- [Persistent Storage 兼容性](/zh/compatibility/persistent-storage/)