localStorage 与 sessionStorage 怎么存 JSON 数据?存储 API 完整用法
【免费下载链接】33-js-concepts📜 33 JavaScript concepts every developer should know.项目地址: https://gitcode.com/GitHub_Trending/33/33-js-concepts
在浏览器应用里保存用户偏好、表单草稿或最近浏览记录时,常用的就是 Web Storage:localStorage和sessionStorage。但这两个 API 只能存字符串——直接把对象传给setItem()会得到"[object Object]",数据就丢了。33-js-concepts 项目的 localStorage & sessionStorage 文档给出了完整做法:写入前用JSON.stringify()序列化,读取时用JSON.parse()还原,并用 try-catch 兜底。本文按“选对存储 → 掌握 API → 正确存取 JSON → 验证”的顺序,把文档中的用法串成一条可执行路径。
前置条件
- 浏览器:Web Storage 属于 HTML5 规范中的 "Baseline" 特性,按文档说明自 2015 年起在所有主流浏览器可用,无需额外安装任何东西,在支持
localStorage/sessionStorage的页面环境(浏览器页面、或 jsdom 这类 DOM 测试环境)中即可使用。 - 如果要复跑项目自带的测试:仓库使用 vitest(devDependencies 中为
vitest ^4.0.16)+jsdom ^27.4.0,先执行npm install安装依赖。 - 知识准备:文档标注此主题假设你熟悉 DOM 和基础 JavaScript 对象,JSON 序列化细节可参考 JSON Deep Dive。
先选对存储:localStorage 还是 sessionStorage
两者 API 完全相同,区别在生命周期和作用域(来自文档的对比表):
| 特性 | localStorage | sessionStorage |
|---|---|---|
| 持久性 | 直到显式清除 | 标签页/窗口关闭时清除 |
| 作用域 | 同源所有标签页/窗口共享 | 隔离在单个标签页 |
| 浏览器重启后保留 | 是 | 否 |
| 页面刷新后保留 | 是 | 是 |
| 容量限制 | 每 origin 约 5–10 MB | 每 origin 约 5–10 MB |
| 可访问方 | 同源任意标签页 | 仅创建它的标签页 |
这里的 origin 指协议 + 域名 + 端口。文档给出的选型示例:
- 用 localStorage:用户偏好(主题、语言、字号)、最近浏览项、feature flag / A/B 分组。
- 用 sessionStorage:不应跨会话保留的表单数据(如
formDraft)、临时导航状态(滚动位置、上次搜索词)、一次性提示标记。
Web Storage API 全集
localStorage和sessionStorage都实现Storage接口,方法一致,共 6 个:
| 成员 | 作用 | 文档给出的示例输出(文档示例) |
|---|---|---|
setItem(key, value) | 存键值对,key 已存在则覆盖 | setItem("username", "alice")后再setItem("username", "bob"),getItem("username")为"bob" |
getItem(key) | 取值,key 不存在返回null | getItem("nonexistent")为null |
removeItem(key) | 删除单个键值对 | 删除后getItem("username")为null |
clear() | 删除该存储中全部键值对,谨慎使用 | clear()后length为0 |
key(index) | 返回指定下标的 key,用于遍历;顺序不保证 | key(99)越界返回null |
length | 已存条目数 | 存入 2 项后localStorage.length为2 |
文档中的完整演示(节选关键调用,注释为文档给出的预期结果):
// 先清空上一轮数据 localStorage.clear() // 存 3 项 localStorage.setItem("name", "Alice") localStorage.setItem("role", "Developer") localStorage.setItem("level", "Senior") console.log("Items stored:", localStorage.length) // 文档示例:3 // 覆盖更新 localStorage.setItem("level", "Lead") console.log("Updated level:", localStorage.getItem("level")) // 文档示例:"Lead" // 用 key(index) + length 遍历全部条目 for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i) const value = localStorage.getItem(key) console.log(`${key}: ${value}`) } // 删单项后 length 为 2,clear() 后为 0 localStorage.removeItem("role") console.log("After removal:", localStorage.length) // 文档示例:2 localStorage.clear() console.log("After clear:", localStorage.length) // 文档示例:0getItem返回null时,文档给出的常用默认值写法是||兜底:
const theme = localStorage.getItem("theme") || "light"核心:JSON 数据的存取方式
为什么不能直接存
Web Storage 只接受字符串。文档列出的直接存非字符串类型的实际结果(文档示例):
localStorage.setItem("count", 42) typeof localStorage.getItem("count") // "string",值是 "42" localStorage.setItem("isActive", true) localStorage.getItem("isActive") // "true"(字符串,不是布尔) // 对象变 "[object Object]"——数据丢失 localStorage.setItem("user", { name: "Alice" }) localStorage.getItem("user") // "[object Object]" // 数组变成逗号分隔字符串 localStorage.setItem("items", [1, 2, 3]) localStorage.getItem("items") // "1,2,3"(字符串,不是数组)正确做法:JSON.stringify / JSON.parse
存时用JSON.stringify(),取时用JSON.parse():
// 存对象 const user = { name: "Alice", age: 30, roles: ["admin", "user"] } localStorage.setItem("user", JSON.stringify(user)) // 取对象 const storedUser = JSON.parse(localStorage.getItem("user")) console.log(storedUser.name) // 文档示例:"Alice" console.log(storedUser.roles) // 文档示例:["admin", "user"] // 存数组 const favorites = ["item1", "item2", "item3"] localStorage.setItem("favorites", JSON.stringify(favorites)) const storedFavorites = JSON.parse(localStorage.getItem("favorites")) console.log(storedFavorites[0]) // 文档示例:"item1"读取时的 null 处理
getItem对不存在的 key 返回null,直接JSON.parse(null)得到null,再访问属性会抛TypeError。文档给出的安全写法是给JSON.parse一个字符串默认值:
// 危险写法:settings 为 null 时 settings.theme 抛 TypeError // const settings = JSON.parse(localStorage.getItem("settings")) // 安全写法:提供默认值 const settings = JSON.parse(localStorage.getItem("settings")) || {} const theme = settings.theme || "light" // 数组同理,默认给 "[]" const recent = JSON.parse(localStorage.getItem("recentlyViewed") || "[]")JSON 序列化的四个坑
文档明确列出的限制(文档示例):
// 1. Date 变字符串,需手动还原 const data = { created: new Date() } localStorage.setItem("data", JSON.stringify(data)) const parsed = JSON.parse(localStorage.getItem("data")) console.log(typeof parsed.created) // "string",不是 Date parsed.created = new Date(parsed.created) // 文档给出的还原方式 // 2. undefined 值直接丢失 JSON.stringify({ a: 1, b: undefined }) // '{"a":1}'——b 没了 // 3. 函数不可序列化 JSON.stringify({ greet: () => "hello" }) // '{}' // 4. 循环引用直接抛错 const circular = { name: "test" } circular.self = circular JSON.stringify(circular) // TypeError: Converting circular structure to JSON因此不要用 Web Storage 存包含函数或自引用的结构;Date 字段在读取侧统一用new Date(...)重建。
封装一个安全的存储工具
文档给出一个自动处理 JSON 和异常的工具对象,可作为业务代码里的主力路径:
const storage = { set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)) return true } catch (error) { console.error("Storage set failed:", error) return false } }, get(key, defaultValue = null) { try { const item = localStorage.getItem(key) return item ? JSON.parse(item) : defaultValue } catch (error) { console.error("Storage get failed:", error) return defaultValue } }, remove(key) { localStorage.removeItem(key) }, clear() { localStorage.clear() } } // 使用:文档示例 storage.set("user", { name: "Alice", premium: true }) const user = storage.get("user") // 文档示例:{ name: "Alice", premium: true } const missing = storage.get("nonexistent", { guest: true }) // 文档示例:{ guest: true }get的第二个参数是默认值:key 不存在或存的不是合法 JSON 时返回它而不是抛错。
验证:跑项目自带的测试用例
仓库中有一整套针对上述用法的测试文件 tests/beyond/browser-storage/localstorage-sessionstorage/localstorage-sessionstorage.test.js,文件头部用@vitest-environment jsdom声明运行在 jsdom 环境(vitest.config.js默认环境是 node,该文件自身覆盖了),从而提供localStorage/sessionStorage。
在仓库根目录执行:
npm install npx vitest run tests/beyond/browser-storage/localstorage-sessionstorage/localstorage-sessionstorage.test.js说明:npx vitest run <文件路径>是 vitest 按文件过滤的用法,只跑这一个测试文件;package.json里的npm test脚本对应vitest run,会执行tests/下全部测试(包含其他主题),验证本主题时建议用上面的过滤命令。
该测试文件覆盖的断言包括:直接存对象得到"[object Object]"、存数组得到"1,2,3";JSON.stringify/parse后对象、数组、嵌套对象可原样取回;Date 被转成字符串、undefined/函数被丢弃、循环引用抛TypeError;封装工具的默认值回退(key 不存在或非法 JSON 时返回defaultValue);key()、length、遍历模式;以及storageAvailable特性检测函数。成功条件:vitest 输出中该文件的测试全部通过、无失败项。
不想跑测试时,也可以直接在浏览器 DevTools 控制台执行本文代码块验证:存对象后console.log取回的值应能访问.name等属性;localStorage.length应随存取增减。
可选用法:storage 事件做跨标签页同步
当同一 origin 的另一个标签页修改了存储,当前页面会收到storage事件。文档特别强调:事件不会在发起修改的标签页触发,只在其他标签页触发。
window.addEventListener("storage", (event) => { console.log("Key:", event.key) // 被修改的 key(clear() 时为 null) console.log("Old value:", event.oldValue) // 旧值(新 key 时为 null) console.log("New value:", event.newValue) // 新值(删除 key 时为 null) console.log("URL:", event.url) // 发起修改的文档 URL console.log("Storage area:", event.storageArea) // 被修改的 Storage 对象 })文档给出的手动验证步骤:
- 两个标签页打开同一站点;
- 两边都打开 DevTools 控制台;
- 在 Tab 1 添加监听器:
window.addEventListener("storage", (e) => console.log("Changed:", e.key)); - 在 Tab 2 执行
localStorage.setItem("test", "value"); - Tab 1 控制台输出
Changed: test(文档示例)。
文档还给了一个实例:监听authToken被其他标签页删除(event.newValue === null)时跳转/login,实现跨标签页登出同步。
配额、隐私模式与特性检测
容量与 QuotaExceededError
文档给出的各浏览器限额均为每 origin 约 5 MB(Chrome / Firefox / Safari / Edge),且 quota 按 origin 计算——https://example.com下所有页面共享同一额度。超限后setItem()抛QuotaExceededError,文档给出的处理写法:
function safeSetItem(key, value) { try { localStorage.setItem(key, value) return true } catch (error) { if (error.name === "QuotaExceededError") { console.error("Storage quota exceeded!") // 文档建议:清理旧数据、提示用户等 return false } throw error // 非配额错误重新抛出 } }数据超过 5 MB 或需要索引查询时,文档的选型表建议改用 IndexedDB(参见 IndexedDB),不要把大数据塞进 Web Storage。
隐私浏览模式
文档列出的差异:Safari 隐私模式下任何写入都会抛QuotaExceededError;Chrome、Firefox、Edge 隐私模式下 localStorage 可用,但窗口关闭时清空;所有浏览器的 sessionStorage 在隐私模式下均可用、关闭时清空。
特性检测
所以文档要求先检测可用性再使用:
function storageAvailable(type) { try { const storage = window[type] const testKey = "__storage_test__" storage.setItem(testKey, testKey) storage.removeItem(testKey) return true } catch (error) { return ( error instanceof DOMException && error.name === "QuotaExceededError" && // 已有存量数据时,QuotaExceededError 仍可视为可用 storage && storage.length !== 0 ) } } if (storageAvailable("localStorage")) { localStorage.setItem("key", "value") } else { console.warn("localStorage not available") // 文档建议:回退到 cookies、内存存储或提示用户 }安全边界
文档明确警告:Web Storage 不做访问隔离,页面上运行的任何 JS(包括 XSS 注入的脚本)都能读取全部条目,因此:
- 密码、支付信息、身份证号、认证 token、API key一律不存Web Storage;认证 token 改用 HTTP-only cookies;
- 只存非敏感数据:偏好、UI 状态、公开的缓存数据;
- 同时建议启用 CSP、消毒所有用户输入。
限制回顾
- Web Storage 是同步API,大数据量操作会阻塞主线程(文档原话提醒);
- 只能存字符串,对象/数组必须经
JSON.stringify;Date、undefined、函数、循环引用无法经 JSON 保真往返; - 每 origin 约 5 MB(各浏览器均约 5 MB),超限额要捕获
QuotaExceededError; localStorage跨标签页共享、sessionStorage按标签页隔离;storage事件只通知其他标签页,当前标签页内不会有回显,同一标签页内的联动逻辑需要自行实现。
【免费下载链接】33-js-concepts📜 33 JavaScript concepts every developer should know.项目地址: https://gitcode.com/GitHub_Trending/33/33-js-concepts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考