焦作市网站建设哪家好,人才网站建设,建设云网站,西安wordpress建站在构建该 Web 应用程序#xff0c;并且希望将数据存储在用户浏览器中。也许您只需要存储一些小标志#xff0c;或者甚至需要一个成熟的数据库。
我们构建的 Web 应用程序类型发生了显着变化。在网络发展的早期#xff0c;我们提供静态 html 文件。然后我们提供动态渲染的 h…在构建该 Web 应用程序并且希望将数据存储在用户浏览器中。也许您只需要存储一些小标志或者甚至需要一个成熟的数据库。
我们构建的 Web 应用程序类型发生了显着变化。在网络发展的早期我们提供静态 html 文件。然后我们提供动态渲染的 html后来我们构建在客户端上运行大部分逻辑的单页应用程序。在未来几年中您可能希望构建所谓的本地优先应用程序仅在客户端上处理大而复杂的数据操作甚至可以在离线状态下工作这使您有机会构建零延迟的用户交互。
在网络的早期 cookie是存储小型键值分配的唯一选择。但是 JavaScript 和浏览器已经发生了显着的发展并且添加了更好的存储 API这为更大、更复杂的数据操作铺平了道路。
在本文中我们将深入探讨可用于在浏览器中存储和查询数据的各种技术。我们将探索传统方法如Cookies 、 localStorage 、 WebSQL 、 IndexedDB和较新的解决方案如通过 WebAssembly 的OPFS和SQLite 。我们比较功能和限制并通过性能测试来揭示使用各种方法在 Web 应用程序中写入和读取数据的速度。 现代浏览器中可用的存储API
首先让我们简要概述不同的API它们的意图用例和历史
什么是Cookies
Cookies是netscape在1994年首次引入的。Cookie存储主要用于会话管理、个性化和跟踪的小块键值数据。Cookie可以有几个安全设置如生存时间或domain属性以便在几个子域之间共享Cookie。
Cookie值不仅存储在客户端而且还随每个http请求发送到服务器。这意味着我们不能在cookie中存储太多数据但与其他方法相比cookie访问性能仍然很有趣。特别是因为cookie是Web的重要基础功能许多性能优化已经完成即使在这些天仍然有进展如chromium的共享内存版本控制或异步CookieStore API。
什么是LocalStorage
localStorage API最初是在2009年作为WebStorage规范的一部分提出的。LocalStorage提供了一个简单的API来在Web浏览器中存储键值对。它有方法setItem、getItem、removeItem和clear这是你从键值存储中所需要的。LocalStorage仅适合存储需要跨会话持久化的少量数据并且受到5 MB存储上限的限制。存储复杂数据只能通过将其转换为字符串来实现例如使用JSON.stringify()。API不是异步的这意味着如果完全阻止你的JavaScript进程而做的事情。因此在其上运行繁重的操作可能会阻止您的UI呈现。 还有SessionStorage API。关键的区别在于localStorage数据无限期地保持直到显式清除而sessionStorage数据在浏览器选项卡或窗口关闭时被清除。 什么是IndexedDB
IndexedDB于2015年首次作为“索引数据库API”引入。
IndexedDB是一个用于存储大量结构化JSON数据的低级API。虽然API有点难以使用但IndexedDB可以利用索引和异步操作。它缺乏对复杂查询的支持只允许覆盖索引这使得它更像是其他库的基础层而不是一个完全成熟的数据库。
2018年IndexedDB 2.0版本发布。这增加了一些重大改进。最值得注意的是getAll()方法它在获取大量JSON文档时显着提高了性能。
IndexedDB3.0版本正在运行中其中包含许多改进。最重要的是添加了基于Promise的调用使现代JS功能如async/await更加有用。
什么是OPFS
Origin Private File SystemOPFS是一个相对较新的API它允许Web应用程序直接在浏览器中存储大型文件。它是为数据密集型应用程序而设计的这些应用程序希望在模拟文件系统中写入和读取二进制数据。
OPFS可以在两种模式下使用
或者在主线程上异步或者在WebWorker中使用createSyncAccessHandle()方法进行更快的异步访问。
由于只能处理二进制数据因此OPFS被用作库开发人员的基本文件系统。在构建“普通”应用程序时您不太可能直接在代码中使用OPFS因为它太复杂了。这只对存储像图像这样的普通文件有意义而不是有效地存储和查询JSON数据。我已经为RxDB构建了一个基于OPFS的存储并使用了适当的索引和查询这花了我几个月的时间。
什么是WASM SQLite
WebAssemblyWasm是一种二进制格式允许在Web上执行高性能代码。Wasm在2017年被添加到主要浏览器中这为在浏览器中运行什么打开了广泛的机会。您可以将原生库编译为WebAssembly并只需进行一些调整即可在客户端上运行它们。WASM代码可以被发送到浏览器应用程序并且通常比JavaScript运行得更快但仍然比原生代码慢10%左右。
许多人开始使用编译的SQLite作为浏览器内部的数据库这就是为什么将此设置与本机API进行比较是有意义的。
SQLite编译后的字节码大小约为938.9 kB用户必须在第一次加载页面时下载并解析。WASM无法直接访问浏览器中的任何持久存储API。相反它需要数据从WASM流到主线程然后可以放入浏览器API之一。这是通过所谓的VFS虚拟文件系统适配器来完成的这些适配器处理从SQLite到其他任何东西的数据访问。
什么是WebSQL
WebSQL是2009年推出的一种Web API允许浏览器使用SQL数据库进行客户端存储基于SQLite。这个想法是给开发人员一种在客户端使用SQL存储和查询数据的方法类似于服务器端数据库。WebSQL近年来已经从浏览器中删除原因有很多
WebSQL尚未标准化并且基于SQLite源代码形式的单个特定实现的API很难成为标准。WebSQL要求浏览器使用特定版本的SQLite版本3.6.19这意味着无论何时SQLite有任何更新或错误修复都不可能在不破坏Web的情况下将其添加到WebSQL。像Firefox这样的主流浏览器从来不支持WebSQL。
因此在下文中我们将忽略WebSQL即使可以通过设置特定的浏览器标志或使用旧版本的chromium来运行测试。 功能比较
现在您已经了解了API的基本概念让我们比较一些特定的功能这些功能对于使用RxDB和基于浏览器的存储的人来说非常重要。
存储复杂的JSON文档
当您在Web应用程序中存储数据时通常您希望存储复杂的JSON文档而不仅仅是存储在服务器端数据库中的integers和strings等“正常”值。
只有IndexedDB可以原生地处理JSON对象。使用SQLite WASM您可以将JSON存储在3.38.02022-02-22版以来的text列中甚至可以对其运行深度查询并使用单个属性作为索引。
其他API只能存储字符串或二进制数据。当然您可以使用JSON.stringify()将任何JSON对象转换为字符串但在API中没有JSON支持会使运行查询时变得复杂并且多次运行JSON.stringify()会导致性能问题。
多选项卡支持
与Electron或React-Native相比构建Web应用程序的一个很大区别是用户将同时在多个浏览器选项卡中打开和关闭应用程序。因此您不仅有一个JavaScript进程在运行而且有许多进程可以存在并且可能必须在彼此之间共享状态更改以免向用户显示过时的数据。 如果你的用户的肌肉记忆在使用你的网站时把左手放在F5键上那么你做错了 并非所有的存储API都支持在选项卡之间自动共享写入事件。
只有localstorage可以通过API本身和storage-event在选项卡之间自动共享写事件storage-event可用于观察更改。 // localStorage can observe changes with the storage event.
// This feature is missing in IndexedDB and others
addEventListener(storage, (event) {}); Chrome有实验性的IndexedDB观察者API但提案存储库已经存档。
要解决此问题有两种解决方案
第一个选项是使用BroadcastChannel API它可以在浏览器选项卡之间发送消息。因此每当您对存储执行写入操作时您也会向其他选项卡发送通知以告知它们这些更改。这是RxDB也使用的最常见的解决方法。请注意WebLocks API其可用于在浏览器选项卡之间具有互斥。另一种解决方案是使用SharedWorker并在worker内部执行所有写入操作。然后所有浏览器选项卡都可以订阅来自单个SharedWorker的消息并了解更改。
索引支持
数据库和将数据存储在普通文件中的最大区别在于数据库以允许在索引上运行操作的格式写入数据以促进快速性能查询。在我们的技术列表中只有IndexedDB和WASM SQLite支持开箱即用的索引。从理论上讲你可以在任何存储如localstorage或OPFS上构建索引但你可能不想自己这样做。
例如在IndexedDB中我们可以通过给定的索引范围获取大量文档 // find all products with a price between 10 and 50
const keyRange IDBKeyRange.bound(10, 50);
const transaction db.transaction(products, readonly);
const objectStore transaction.objectStore(products);
const index objectStore.index(priceIndex);
const request index.getAll(keyRange);
const result await new Promise((res, rej) {request.onsuccess (event) res(event.target.result);request.onerror (event) rej(event);
}); 请注意IndexedDB的限制是不能对布尔值建立索引。只能索引字符串和数字。为了解决这个问题你必须在存储数据时将布尔值转换为数字并向后转换。
WebWorker支持
当运行繁重的数据操作时您可能希望将处理从JavaScript主线程移开。这确保了我们的应用程序保持快速响应同时处理可以在后台并行运行。在浏览器中您可以使用WebWorker、SharedWorker或ServiceWorkerAPI来完成此操作。在RxDB中您可以使用WebWorker或SharedWorker插件将您的存储移动到worker中。
该用例最常见的API是生成一个WebWorker并在第二个JavaScript进程上执行大部分工作。worker从一个单独的JavaScript文件或base64字符串派生并通过使用postMessage()发送数据与主线程通信。
不幸的是由于设计和安全限制LocalStorage和Cookie不能在WebWorker或SharedWorker中使用。WebWorkers在与主浏览器线程不同的全局上下文中运行因此不能做可能影响主线程的事情。他们无法直接访问某些Web API如DOM、localStorage或Cookie。
其他一切都可以从WebWorker内部使用。带有createSyncAccessHandle方法的OPFS的快速版本只能在WebWorker中使用而不能在主线程上使用。这是因为返回的AccessHandle的所有操作都不是JavaScript因此会阻塞JavaScript进程所以您确实希望在主线程上执行此操作并阻塞所有操作。 存储大小限制 Cookie仅限于RFC-6265中的4 KB数据。由于存储的cookie随每个HTTP请求发送到服务器因此此限制是合理的。您可以在这里测试您的浏览器cookie限制。请注意您不应该填写完整的4 KBcookie因为您的Web服务器不会接受太长的标题并拒绝使用HTTP ERROR 431 - Request header fields too large的请求。一旦你达到了这一点你甚至不能提供更新的JavaScript给你的用户来清理cookie你将锁定该用户直到cookie得到手动清理。 LocalStorage的存储大小限制因浏览器而异但通常每个源的范围为4 MB至10 MB。您可以在这里测试您的localStorage大小限制。 Chrome/Chromium/Edge每个域5 MBFirefox每个域名10 MBSafari每个域4-5 MB不同版本略有不同 IndexedDB没有像localStorage那样的固定大小限制。IndexedDB的最大存储大小取决于浏览器实现。上限通常基于用户设备上的可用磁盘空间。在铬浏览器中它可以使用高达80%的总磁盘空间。您可以通过调用await navigator.storage.estimate()获得关于存储大小限制的估计。通常您可以存储千兆字节的数据可以在这里尝试。 OPFS具有与IndexedDB相同的存储大小限制。其限制取决于可用的磁盘空间。这也可以在这里测试。 性能比较
现在我们已经回顾了每种存储方法的特性让我们深入到性能比较重点是初始化时间读/写延迟和批量操作。
请注意我们只运行简单的测试对于您应用程序中的特定用例结果可能会有所不同。此外我们只比较性能在谷歌Chrome版本128.0.6613.137。Firefox和Safari具有相似但不相等的性能模式。你可以在你自己的机器上从这个github仓库运行测试。在所有的测试中我们都将网络节流使其表现得像德国的平均互联网速度。下载135900 kbit/s上传28400 kbit/s延迟125 ms。此外所有测试都存储一个“平均”JSON对象根据存储情况可能需要对该对象进行字符串化。我们也只测试通过id存储文档的性能因为一些技术cookieOPFS和localstorage不支持索引范围操作所以比较这些技术的性能没有意义。
初始化时间
在存储任何数据之前许多API都需要一个设置过程例如创建数据库生成WebAssembly进程或下载其他内容。为了确保应用快速启动初始化时间非常重要。
localStorage和Cookie的API没有任何设置过程可以直接使用。IndexedDB需要打开一个数据库和其中的存储。WASM SQLite需要下载一个WASM文件并处理它。OPFS需要下载并启动一个worker文件并初始化虚拟文件系统目录。
以下是从存储第一位数据所需的时间测量
技术时间毫秒IndexedDB46OPFS主线程23OPFS WebWorker的使用26.8WASM SQLite内存504WASM SQLiteIndexedDB535
在这里我们可以注意到几件事
使用单个存储打开新的IndexedDB数据库需要花费惊人的时间从主线程向WebWorker OPFS发送数据的延迟开销大约为4毫秒。在这里我们只发送最少的数据来初始化OPFS文件处理程序。当处理更多的数据时如果延迟增加那将是有趣的。下载和解析WASM SQLite并创建一个表大约需要半秒钟。使用IndexedDBVFS持久存储数据还额外增加了31毫秒。使用启用的缓存和已经准备好的表来访问页面要快一些需要420毫秒内存。
小写入延迟
接下来让我们测试小写入的延迟。当您进行许多相互独立的小数据更改时这一点很重要。就像你从WebSocket中传输数据或者持久化伪随机发生的事件比如鼠标移动。 技术时间毫秒Cookies0.058LocalStorage本地存储0.017IndexedDB0.17OPFS主线程1.46OPFS WebWorker的使用1.54WASM SQLite内存0.17WASM SQLiteIndexedDB3.17 在这里我们可以注意到几件事
LocalStorage具有最低的写入延迟每次写入仅为0.017毫秒。IndexedDB的写入速度比localStorage慢10倍。将数据发送到WASM SQLite进程并让其通过IndexedDB持久化是很慢的每次写入超过3毫秒。
OPFS操作将JSON数据写入每个文件的一个文档大约需要1.5毫秒。我们可以看到首先将数据发送到Webworker有点慢这是由于在两端序列化和重新序列化数据的开销。如果我们不在每个文档上创建OPFS文件而是将所有内容附加到单个文件中则性能模式会发生显著变化。然后来自createSyncAccessHandle()的更快的文件句柄每次写入只需要大约1毫秒。但这需要以某种方式记住每个文档存储在哪个位置。因此在我们的测试中我们将继续使用每个文档一个文件。
小读取延迟
现在我们已经存储了一些文档让我们测量一下读取单个文档所需的时间。
技术时间毫秒Cookies0.132LocalStorage本地存储0.0052IndexedDB0.1OPFS主线程1.28OPFS WebWorker的使用1.41WASM SQLite内存0.45WASM SQLiteIndexedDB2.93
在这里我们可以注意到几件事
LocalStorage读取速度非常快每次读取仅为0.0052毫秒。其他技术执行读取的速度与其写入延迟相似。
大批量写入
下一步让我们一次对200个文档进行一些大批量操作。 技术时间毫秒Cookies20.6LocalStorage本地存储5.79IndexedDB13.41OPFS主线程280OPFS WebWorker的使用104WASM SQLite内存19.1WASM SQLiteIndexedDB37.12
在这里我们可以注意到几件事
将数据发送到WebWorker并通过更快的OPFS API运行它的速度大约是原来的两倍。与单次写入延迟相比WASM SQLite在批量操作上的性能更好。这是因为如果一次性完成而不是每个文档一次则将数据发送到WASM并向后发送会更快。
大批量读取
现在让我们在批量请求中读取100个文档。 技术时间毫秒Cookies6.34LocalStorage本地存储0.39IndexedDB4.99OPFS主线程54.79OPFS WebWorker的使用25.61WASM SQLite内存3.59WASM SQLiteIndexedDB5.84 (35ms无缓存
在这里我们可以注意到几件事
在OPFS webworker中阅读许多文件的速度大约是较慢的主线程模式的两倍。WASM SQLite速度惊人。进一步的检查表明WASM SQLite进程将文档保存在内存中缓存这改善了我们在对同一数据进行写入后直接读取时的延迟。当浏览器选项卡在写入和读取之间重新加载时查找这100个文档大约需要35毫秒。
性能结论
LocalStorage确实很快但请记住它有一些缺点 它会阻塞主JavaScript进程因此不应用于大批量操作。只有键值赋值是可能的当你需要对数据进行基于索引的范围查询时你不能有效地使用它。与直接在主线程中使用OPFS相比在WebWorker中使用createSyncAccessHandle()方法时OPFS要快得多。SQLite WASM可以很快但你必须首先下载完整的二进制文件并启动它这大约需要半秒钟。如果您的应用程序启动一次并使用很长一段时间这可能根本不相关。但是对于在许多浏览器标签中打开和关闭多次的网络应用程序来说这可能是一个问题。 可能改进
有多种可能的改进和性能改进来加速操作。
对于 IndexedDB我在这里列出了性能技巧的列表。例如您可以在多个数据库和网络工作者之间进行分片或使用自定义索引策略。OPFS 在每个文档写入一个文件时速度很慢。但您不必这样做您可以像普通数据库一样将所有内容存储在单个文件中。这极大地提高了性能就像使用 RxDB OPFS RxStorage所做的那样。您可以混合使用这些技术来同时针对多个场景进行优化。例如在 RxDB 中有localstorage 元优化器它将初始元数据存储在 localstorage 中并将“普通”文档存储在 IndexedDB 内。这缩短了初始启动时间同时仍然以高效查询的方式存储文档。RxDB中有内存映射存储插件可以将数据直接映射到内存。将此与共享工作程序结合使用可以显着改善页面加载和查询时间。在存储数据之前对其进行压缩可能会提高某些存储的性能。通过分片在多个 WebWorker 之间分割工作可以利用用户设备的全部容量来提高性能。
在这里您可以看到各种RxDB存储实现的性能比较这可以更好地了解真实的性能 未来改进
你正在2024年阅读这篇文章但网络并没有停滞不前。有一个很好的机会浏览器得到增强以允许更快更好的数据操作。
目前还没有办法从WebAssembly进程内部直接访问持久存储。如果将来发生变化在浏览器中运行SQLite或类似的数据库可能是最好的选择。在主线程和WebWorker之间发送数据很慢但将来可能会得到改进。有一个很好的文章关于为什么postMessage()是缓慢的。IndexedDB最近支持存储桶仅限Chrome这可能会提高性能