当谈到 Web 应用的客户端存储时,localStorage
API 脱颖而出,它允许开发者直接在浏览器中存储键值对。
在开发 Vue 应用时,我们有时候需要将用户数据保存在本地,实现持久化存储。我们可以自己编写存储功能,也可以使用 Pinia 生态的存储插件,比如目前人气最高的 pinia-plugin-persistedstate
,而这个插件的默认存储方案就是基于 localStorage
实现的。
在本文中,我们会深度学习 localStorage
API 的优缺点和其他替代存储方案。
localStorage
API 是浏览器的内置功能,使开发者能够在用户设备上持久存储少量数据。
localStorage
基于简单的键值对运行,允许开发者保存字符串等原始数据类型。即使用户关闭浏览器或离开页面,这些数据仍然可用。
localStorage
提供了一种便捷方案来维护状态和存储用户首选项,而无需依赖服务器端存储。
localStorage
提供了多种交互方法,包括但不限于:
setItem
getItem
removeItem
clear
举个栗子,代码如下所示:
// 使用 setItem 存储数据
localStorage.setItem('username', 'john_doe')
// 使用 getItem 检索数据
const storedUsername = localStorage.getItem('username')
// 使用 removeItem 删除数据
localStorage.removeItem('username')
// 清空所有数据
localStorage.clear()
虽然 localStorage
擅长处理简单键值对,但它还通过 JSON
序列化支持更复杂的数据存储。利用 JSON.stringify
和 JSON.parse
,我们可以存储和检索结构化数据,比如对象和数组。
举个栗子,代码如下所示:
const cat = {
name: '薛定谔',
age: 18
}
// 存储 cat 对象
localStorage.setItem('cat', JSON.stringify(cat))
// 检索并解析 cat 对象
const storedCat = JSON.parse(localStorage.getItem('cat'))
尽管 localStorage
十分便捷,但它存在某些限制:
localStorage
作为非异步阻塞 API 运行。这意味着,localStorage
执行的任何操作都可能会阻塞主线程,降低应用程序性能和响应速度,影响用户体验。localStorage
仅限于简单的键值存储。这种限制使得它不适合存储负载的数据结构,或管理数据元素之间的关系。localStorage
存储 JSON
数据需要先对数据字符串化,且在检索时需要先解析。这个过程会带来性能开销,可能会使操作速度减慢高达 10
倍。localStorage
缺乏索引功能,很难根据特定条件执行有效搜索。这个限制会阻碍依赖复杂数据检索的应用程序。localStorage
操作可能会独占 CPU 资源,影响其他页面的性能。localStorage
数据源施加大约 5
MiB 的存储限制。与 IndexedDB 替代存储解决方案相比,localStorage
API 速度惊人。localStorage
擅长高效处理迷你键值赋值。由于其简单性以及与浏览器的直接集成,修改 localStorage
数据的开销最小。
对于快速简单的数据存储场景,localStorage
仍然是一个不错的选择。
虽然 localStorage
十分便捷,但它可能并不适合所有场景。
考虑以下情况,其他替代方案可能更合适:
localStorage
无法提供必要的查询功能。数据检索可能导致代码效率低下和性能下降。JSON
文档:localStorage
存储大型 JSON
文档会消耗大量内存,并降低性能。localStorage
上过多的读写操作会导致性能瓶颈。Map/Set
,为瞬态数据提供速度和效率。localStorage
vs IndexedDB
IndexedDB
既可以存储键值对,也可以存储 JSON
文档。
与 localStorage
通常每个域名的存储限制约为 5-10MB 不同,IndexedDB 可以处理更大的数据集,且其对索引的支持可以高效查询。
但粉丝请注意,IndexedDB
缺乏可观察性,这是 localStorage
通过 storage
事件的专属功能。
此外,虽然 IndexDB
复杂查询的性能差强人意,但对于某些场景而言 IndexedDB
可能太慢。
// localStorage 通过 storage 事件监测变化
// IndexedDB 缺少监测功能
addEventListener('storage', event => {})
对于那些希望利用 IndexedDB
的全部功能的人而言,建议使用 RxDB 或 Dexie.js 等封装库。这些库通过复杂查询和可观察性等功能强化了 IndexedDB
。
另一个知识盲区是 OPFS(源私有文件系统)。这个 API 提供对基于源的沙盒文件系统的直接访问,该文件系统针对性能高度优化,并提供对其内容的就地写入访问。
OPFS 提供了令人印象深刻的性能优势。然而,使用 OPFS API 可能十分复杂,而且能且仅能在 WebWorker
中访问。
localStorage
vs CookieCookie 曾经是客户端数据存储的主流方案,但在现代 Web 开发中已经失宠。
虽然 Cookie 可以存储数据,但与 localStorage
相比,Cookie 的速度慢了大约 100
倍。此外,Cookie 包含在 HTTP 请求头中会影响网络性能。
因此,不建议在当代 Web 应用中使用 Cookie 存储数据。
localStorage
vs WebSQL尽管 WebSQL 为客户端数据存储提供了基于 SQL 的接口,但它是一种已废弃的技术。
WebSQL API 已经被现代浏览器淘汰,且缺乏 IndexedDB
等替代方案的鲁棒性。
此外,WebSQL 的速度通常比 IndexedDB
慢 10
倍左右,这使得它对于需要高效数据检索的应用而言不是最佳选择。
localStorage
vs sessionStorage
在不需要会话外的数据持久性的场景下,开发者通常会“切换赛道”到 sessionStorage
。
sessionStorage
能且仅能在标签页或浏览器会话期间保留数据。它可以在页面重载和恢复后继续存在,为临时数据需求提供便捷的解决方案。
对于 React Native 开发者而言,AsyncStorage
API 是首选解决方案,它类似 localStorage
的镜像行为,但具有异步支持。
由于并非所有 JS 运行时都支持 localStorage
,因此 AsyncStorage
为 React Native 应用中的数据持久性提供了无缝集成的替代方案。
node-localstorage
由于 Node 中不存在原生的 localStorage
,因此我们会在 Node 等的运行时收到错误 ReferenceError: localStorage is not defined
。
node-localstorage
模块弥补了这一差距。该模块在 Node 环境中拷贝了浏览器的 localStorage
,确保数据存储功能的一致性。
虽然谷歌 Chrome 和 Firefox 浏览器扩展支持 localStorage
,但在大多数场景下,浏览器都会清除数据,比如当用户清除浏览历史记录时。
相反,Extension Storage
API 应该用于浏览器扩展。与 localStorage
相比,这个 API 的异步执行,且所有操作都会返回 Promise
。
Extension Storage
还提供自动同步功能,以便在用户登录的浏览器的所有实例之间拷贝数据。它甚至能够存储 JSON
格式的对象而不是纯字符串。
Deno 运行时有一个有效的 localStorage
API,因此运行 localStorage.setItem()
等方法会奏效,且 localStorage
的数据会在多次运行中保留。
Bun 目前不支持 localStorage
API。试试就逝世,会直接报错 ReferenceError: Can't find variable: localStorage
。要在 Bun 中本地存储数据,可以使用 bun:sqlite
模块。
在现代 Web 开发领域,localStorage
是轻量级数据的存储神器,其简单性和速度使其成为迷你键值分配的最佳方案。
然而,随着应用复杂性的增加,开发者必须仔细权衡他们的存储需求。
对于需要高级查询、复杂数据结构或大容量操作的场景,IndexedDB 等替代方案、RxDB 等封装库或 Deno 等运行时的 API 可以提供更强大的解决方案。