在浏览器中存储数据有很多不同的选项。哪一个最适合您的需求?
在移动设备上,互联网连接可能不稳定或根本不存在,这就是离线支持和可靠的性能成为渐进式 Web 应用中常见功能的原因。即使在完美的无线环境中,明智地使用缓存和其他存储技术也可以大大改善用户体验。有几种方法可以缓存您的静态应用程序资源(HTML、JavaScript、CSS、图像等)和数据(用户数据、新闻文章等)。但哪种是最佳解决方案?您可以存储多少内容?如何防止其被逐出?
我应该使用什么?
以下是存储资源的一般建议
- 对于加载您的应用所必需的网络资源,请使用 Cache Storage API(Service Worker 的一部分)。
- 对于基于文件的内容,请使用 Origin Private File System (OPFS)。
- 对于其他数据,请使用 IndexedDB(带有 promise 封装器)。
IndexedDB、OPFS 和 Cache Storage API 在所有现代浏览器中均受支持。它们是异步的,并且不会阻塞主线程(但 OPFS 也有同步变体,仅在 Web Worker 中可用)。它们可以从 window
对象、Web Worker 和 Service Worker 访问,从而可以在代码中的任何位置使用它们。
其他存储机制呢?
浏览器中还有其他几种存储机制可用,但它们的使用范围有限,并且可能会导致严重的性能问题。
SessionStorage 是标签页特定的,并且作用域限定为标签页的生命周期。它可能对于存储少量的会话特定信息很有用,例如 IndexedDB 密钥。应谨慎使用它,因为它是同步的,并且会阻塞主线程。它限制为大约 5MB,并且只能包含字符串。由于它是标签页特定的,因此无法从 Web Worker 或 Service Worker 访问。
LocalStorage 应该避免使用,因为它是同步的,并且会阻塞主线程。它限制为大约 5MB,并且只能包含字符串。无法从 Web Worker 或 Service Worker 访问 LocalStorage。
Cookie 有其用途,但不应用于存储。Cookie 随每个 HTTP 请求一起发送,因此存储任何超过少量的数据都会显着增加每个 Web 请求的大小。它们是同步的,并且无法从 Web Worker 访问。与 LocalStorage 和 SessionStorage 一样,Cookie 仅限于字符串。
File System Access API 旨在使用户能够在本地文件系统上读取和编辑文件。用户必须先授予权限,页面才能读取或写入任何本地文件,并且权限不会跨会话持久保存,除非文件句柄缓存在 IndexedDB 中。File System Access API 最适合编辑器等用例,在这些用例中,您需要打开文件、修改文件,然后可能将更改保存回文件。
File System API 和 FileWriter API 提供了用于在沙盒文件系统中读取和写入文件的方法。虽然它是异步的,但不建议使用,因为它仅在基于 Chromium 的浏览器中可用。
我可以存储多少内容?
简而言之,很多,至少几百兆字节,并且可能达到数百千兆字节或更多。浏览器实现各不相同,但可用存储量通常基于设备上可用的存储量。
- Chrome 允许浏览器使用高达 80% 的总磁盘空间。一个来源最多可以使用 60% 的总磁盘空间。您可以使用 StorageManager API 来确定最大可用配额。其他基于 Chromium 的浏览器可能有所不同。
- 在隐身模式下,Chrome 会将来源可以使用的存储量减少到大约总磁盘空间的 5%。
- 如果用户在 Chrome 中启用了“当您关闭所有窗口时清除 Cookie 和站点数据”,则存储配额将显着减少到最多约 300MB。
- Firefox 允许浏览器使用高达 50% 的可用磁盘空间。eTLD+1 组(例如,
example.com
、www.example.com
和foo.bar.example.com
)最多可以使用 2GB。您可以使用 StorageManager API 来确定还剩多少空间。 - Safari(桌面和移动设备)似乎允许大约 1GB。当达到限制时,Safari 会提示用户,以 200MB 的增量增加限制。我找不到有关此的任何官方文档。
- 如果 PWA 添加到移动 Safari 上的主屏幕,它将创建一个新的存储容器,并且 PWA 和移动 Safari 之间不共享任何内容。一旦已安装的 PWA 达到配额,似乎没有任何方法可以请求额外的存储。
过去,如果站点存储的数据超过某个阈值,浏览器会提示用户授予使用更多数据的权限。例如,如果来源使用超过 50MB,浏览器会提示用户允许其存储最多 100MB,然后在 50MB 增量时再次询问。
如今,大多数现代浏览器都不会提示用户,并且会允许站点使用高达其分配的配额。例外情况似乎是 Safari,它会在超出存储配额时提示,请求允许增加分配的配额。如果来源尝试使用超过其分配的配额,则进一步尝试写入数据将失败。
如何检查有多少存储空间可用?
在许多浏览器中,您可以使用 StorageManager API 来确定来源可用的存储量以及它正在使用的存储量。它报告 IndexedDB 和 Cache API 使用的总字节数,并使计算可用的大致剩余存储空间成为可能。
if (navigator.storage && navigator.storage.estimate) {
const quota = await navigator.storage.estimate();
// quota.usage -> Number of bytes used.
// quota.quota -> Maximum number of bytes available.
const percentageUsed = (quota.usage / quota.quota) * 100;
console.log(`You've used ${percentageUsed}% of the available storage.`);
const remaining = quota.quota - quota.usage;
console.log(`You can write up to ${remaining} more bytes.`);
}
您必须捕获超配额错误(请参见下文)。在某些情况下,可用配额可能超过实际可用存储量。
检查
在开发期间,您可以使用浏览器的开发者工具来检查不同的存储类型,并清除所有存储的数据。
Chrome 88 中添加了一项新功能,可让您在“存储”窗格中覆盖站点的存储配额。此功能使您能够模拟不同的设备,并在磁盘可用性较低的情况下测试应用程序的行为。转到应用程序,然后转到存储,启用模拟自定义存储配额复选框,然后输入任何有效数字以模拟存储配额。
在编写本指南时,我编写了一个简单工具,试图快速使用尽可能多的存储空间。这是一种试验不同存储机制并查看当您使用完所有配额时会发生什么的快速方法。
如何处理超出配额的情况?
当您超出配额时应该怎么做?最重要的是,您应该始终捕获并处理写入错误,无论是 QuotaExceededError
还是其他错误。然后,根据您的应用程序设计,决定如何处理它。例如,删除长时间未访问的内容,根据大小删除数据,或者为用户提供一种选择他们想要删除的内容的方式。
当您超出可用配额时,IndexedDB 和 Cache API 都会抛出名为 QuotaExceededError
的 DOMError
。
IndexedDB
如果来源已超出其配额,则尝试写入 IndexedDB 将会失败。将调用事务的 onabort()
处理程序,并传递一个事件。该事件将在 error 属性中包含一个 DOMException
。检查错误 name
将返回 QuotaExceededError
。
const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
const error = event.target.error; // DOMException
if (error.name == 'QuotaExceededError') {
// Fallback code goes here
}
};
Cache API
如果来源已超出其配额,则尝试写入 Cache API 将会拒绝,并出现 QuotaExceededError
DOMException
。
try {
const cache = await caches.open('my-cache');
await cache.add(new Request('/sample1.jpg'));
} catch (err) {
if (error.name === 'QuotaExceededError') {
// Fallback code goes here
}
}
驱逐是如何工作的?
Web 存储分为两个存储桶:“尽力而为”和“持久”。尽力而为意味着浏览器可以在不中断用户的情况下清除存储,但对于长期或关键数据而言,持久性较差。当存储空间不足时,持久存储不会自动清除。用户需要手动清除此存储(通过浏览器设置)。
默认情况下,站点的所有数据(包括 IndexedDB、Cache API 等)都属于“尽力而为”类别,这意味着除非站点请求了持久存储,否则浏览器可能会自行决定逐出站点数据,例如,当设备存储空间不足时。
“尽力而为”的驱逐策略是
- 当浏览器空间不足时,基于 Chromium 的浏览器将开始逐出数据,首先清除最近最少使用的来源的所有站点数据,然后清除下一个,直到浏览器不再超出限制。
- 当可用磁盘空间已满时,Firefox 将开始逐出数据,首先清除最近最少使用的来源的所有站点数据,然后清除下一个,直到浏览器不再超出限制。
- Safari 以前不驱逐数据,但最近对所有可写存储实施了新的七天上限(请参见下文)。
从 iOS 和 iPadOS 13.4 以及 macOS 上的 Safari 13.1 开始,所有脚本可写存储(包括 IndexedDB、Service Worker 注册和 Cache API)都有七天上限。这意味着,如果用户在七天内未使用 Safari 与站点进行交互,Safari 将从缓存中逐出所有内容。此驱逐策略不适用于已添加到主屏幕的已安装 PWA。有关完整详细信息,请参阅 WebKit 博客上的完全阻止第三方 Cookie 及更多。
存储桶
Storage Buckets API 的核心思想是授予站点创建多个存储桶的能力,浏览器可以选择独立于其他存储桶删除每个存储桶。这允许开发者指定驱逐优先级,以确保最有价值的数据不会被删除。
奖励:为什么使用 IndexedDB 的封装器
IndexedDB 是一个低级 API,使用前需要进行大量设置,这对于存储低复杂性数据来说尤其痛苦。与大多数现代基于 Promise 的 API 不同,它是基于事件的。用于 IndexedDB 的 Promise 封装器(如 idb)隐藏了一些强大的功能,但更重要的是,隐藏了 IndexedDB 库附带的复杂机制(例如,事务、架构版本控制)。
奖励:SQLite Wasm
在 Web SQL 被弃用并从 Chrome 中移除后,Google 与流行的 SQLite 数据库的维护者合作,提供了一个基于 SQLite 的 Web SQL 替代方案。阅读 由 Origin Private File System 支持的浏览器中的 SQLite Wasm,了解有关如何使用它的详细信息。
结论
存储空间有限且提示用户存储越来越多数据的日子已经一去不复返了。站点可以有效地存储运行所需的所有资源和数据。使用 StorageManager API,您可以确定有多少空间可供您使用以及您已使用了多少空间。借助持久存储,除非用户删除,否则您可以保护它免受驱逐。
其他资源
感谢
特别感谢 Jarryd Goodman、Phil Walton、Eiji Kitamura、Daniel Murphy、Darwin Huang、Josh Bell、Marijn Kruisselbrink 和 Victor Costan 审阅本指南。感谢 Eiji Kitamura、Addy Osmani 和 Marc Cohen 编写了本指南所依据的原始文章。Eiji 编写了一个名为 Browser Storage Abuser 的有用工具,该工具在验证当前行为方面很有用。它允许您存储尽可能多的数据,并查看浏览器上的存储限制。感谢 François Beaufort 深入研究 Safari 以找出其存储限制,并感谢 Thomas Steiner 添加了有关 Origin Private File System、存储桶、SQLite Wasm 以及 2024 年总体内容更新的信息。
英雄图片由 Guillaume Bolduc 在 Unsplash 上提供。