news 2026/9/9 22:13:08

localStorage 与 sessionStorage 怎么存 JSON 数据?存储 API 完整用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
localStorage 与 sessionStorage 怎么存 JSON 数据?存储 API 完整用法

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:localStoragesessionStorage。但这两个 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 完全相同,区别在生命周期和作用域(来自文档的对比表):

特性localStoragesessionStorage
持久性直到显式清除标签页/窗口关闭时清除
作用域同源所有标签页/窗口共享隔离在单个标签页
浏览器重启后保留
页面刷新后保留
容量限制每 origin 约 5–10 MB每 origin 约 5–10 MB
可访问方同源任意标签页仅创建它的标签页

这里的 origin 指协议 + 域名 + 端口。文档给出的选型示例:

  • 用 localStorage:用户偏好(主题、语言、字号)、最近浏览项、feature flag / A/B 分组。
  • 用 sessionStorage:不应跨会话保留的表单数据(如formDraft)、临时导航状态(滚动位置、上次搜索词)、一次性提示标记。

Web Storage API 全集

localStoragesessionStorage都实现Storage接口,方法一致,共 6 个:

成员作用文档给出的示例输出(文档示例)
setItem(key, value)存键值对,key 已存在则覆盖setItem("username", "alice")后再setItem("username", "bob")getItem("username")"bob"
getItem(key)取值,key 不存在返回nullgetItem("nonexistent")null
removeItem(key)删除单个键值对删除后getItem("username")null
clear()删除该存储中全部键值对,谨慎使用clear()length0
key(index)返回指定下标的 key,用于遍历;顺序不保证key(99)越界返回null
length已存条目数存入 2 项后localStorage.length2

文档中的完整演示(节选关键调用,注释为文档给出的预期结果):

// 先清空上一轮数据 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) // 文档示例:0

getItem返回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 对象 })

文档给出的手动验证步骤:

  1. 两个标签页打开同一站点;
  2. 两边都打开 DevTools 控制台;
  3. 在 Tab 1 添加监听器:window.addEventListener("storage", (e) => console.log("Changed:", e.key))
  4. 在 Tab 2 执行localStorage.setItem("test", "value")
  5. 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 22:13:05

2025年TDD面试实战:Spring Boot单元测试与Mockito最佳实践

“TDD 都写了三四年了&#xff0c;面试官一问还能把我问住&#xff1f;”这是我去年帮一个朋友做模拟面试时&#xff0c;他亲口说的话。他并不是不会写测试&#xff0c;而是在面试高压下&#xff0c;把“写测试”和“TDD”混为一谈&#xff0c;一被追问“先写测试还是先写实现”…

作者头像 李华
网站建设 2026/9/9 22:12:44

seaborn进阶:如何用Python轻松绘制统计图形?

上礼拜有个刚转行做数据分析的朋友问我&#xff1a;matplotlib我都还没学明白&#xff0c;怎么到处都在说seaborn&#xff1f;我给他打了个比方&#xff1a;matplotlib是原木&#xff0c;什么都能做&#xff0c;但所有事都得自己动手&#xff1b;seaborn是给你切好拼好的板材&a…

作者头像 李华
网站建设 2026/9/9 22:11:51

TLS加密套件深度解析:从套件原理到IoT设备选型实战

先交代一下背景。前阵子帮朋友排查一批智能网关设备连不上服务器的问题&#xff0c;抓包一看&#xff0c;握手阶段直接卡在ClientHello和ServerHello之间&#xff0c;服务端报“no shared cipher”。设备端跑的是裁剪过的TLS栈&#xff0c;只支持两三个套件&#xff0c;服务端却…

作者头像 李华
网站建设 2026/9/9 22:11:28

如何用 LightGBM 同时训练多个相关目标:多任务学习实战指南

如何用 LightGBM 同时训练多个相关目标&#xff1a;多任务学习实战指南 【免费下载链接】LightGBM A fast, distributed, high performance gradient boosting (GBT, GBDT, GBRT, GBM or MART) framework based on decision tree algorithms, used for ranking, classification…

作者头像 李华
网站建设 2026/9/9 22:10:43

AI材质烘焙流:从基础图到4K无缝PBR贴图的极速工作流

1. 先聊聊“无缝贴图手绘”这件事有多痛做三维资产的朋友应该都懂&#xff0c;PBR 流程里最磨人的不是模型拓扑&#xff0c;而是那套“无穷无尽”的贴图。尤其做环境资产、建筑部件、地形混合材质的时候&#xff0c;一张无缝贴图要能在平面上四个方向无限拼接不露破绽&#xff…

作者头像 李华
网站建设 2026/9/9 22:07:37

STM32F4串口/RS485 OTA升级方案:Bootloader与Flash分区设计实践

简介&#xff1a;面向STM32嵌入式开发者的OTA升级参考资源&#xff0c;特别适配工业现场通过RS485总线远程维护设备的需求。资源包含自制bootloader与App两套完整Keil工程&#xff0c;演示了从固件分包传输、存储到跳转运行的全链路实现。包内共277个文件&#xff0c;以C/H源码…

作者头像 李华