localStorage超限必然发生,需预判容量、捕获错误、降级存储三者并行:用TextEncoder算UTF-8字节而非String.length,维护sizeEstimate计数器,每次setItem单独try/catch并fallback至sessionStorage或内存Map,大对象优先IndexedDB,清理须按Cache→IndexedDB→localStorage/sessionStorage倒序执行。

localStorage 超限不是“会不会发生”的问题,而是“什么时候发生、怎么不崩”的问题——必须在写入前预判、写入时捕获、失败后降级,三者缺一不可。
怎么预估 localStorage 实际剩余容量
浏览器没提供 localStorage.remainingSpace 这种接口,硬查只能靠试探写入或字节估算。直接用 JSON.stringify() 算字符串长度会错:中文、emoji、控制字符在 UTF-8 下占 2–4 字节,String.length 返回的是 UTF-16 码元数,不是真实字节数。
- 用
new TextEncoder().encode(str).length获取准确 UTF-8 字节长度,再和已知配额(如 5MB = 5242880 字节)比对 - 维护一个本地计数器
localStorage.__sizeEstimate,每次setItem()前加增量、removeItem()后减估算值(比如平均每条 8KB,就 ±8192) - 别依赖“首次写入 1MB 不报错就认为还有 4MB”——Safari 隐身模式可能直接禁用
localStorage,试探写入也会抛出SecurityError而非QuotaExceededError
QuotaExceededError 捕获必须包裹每一次 setItem
很多人只在初始化时 try/catch 一次,后续更新全靠裸调 localStorage.setItem()。一旦某次写入触发配额超限,JS 就会中断,后面逻辑全丢,表单草稿、用户偏好、路由状态都可能丢失。
- 所有
setItem()必须单独 try/catch,且不能只 catchQuotaExceededError—— Safari 可能抛SecurityError,Firefox 在某些策略下抛InvalidStateError - 错误处理不要只打 console,要立刻 fallback:比如降级到
sessionStorage(同源下仍可用)、内存Map(页面生命周期内有效),或把关键字段塞进 URLhash - 避免在循环里批量
setItem():哪怕只有一条失败,整个循环就断,应逐条判断 + 记录失败 key,最后统一提示用户“X 条未持久化”
大对象存 localStorage 是性能陷阱,不是容量陷阱
就算你只存 2MB 数据,如果是个深度嵌套的 5000 行 JSON,JSON.stringify() 和 JSON.parse() 就可能让低端 Android WebView 卡顿 300ms+,用户点击无响应,误以为页面崩溃。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 超过 100KB 的单条数据,优先走
IndexedDB;它异步、支持结构化、序列化压力小 - 若必须用
localStorage,先做浅层 diff:用JSON.stringify(obj, Object.keys(obj).sort())统一键序再比对,避免“内容没变但字符串不同”导致重复写入 - 禁止存
function、undefined、Date对象——JSON.stringify()会静默丢掉它们,读出来是空对象或 null,调试时极难定位
清理逻辑必须覆盖所有存储层,且顺序不能错
用户点“清除本地数据”,结果只清了 localStorage,但 sessionStorage 里还留着半份表单、IndexedDB 里存着旧用户档案、Service Worker 的 Cache 里缓着过期 HTML——这不是清理,是埋雷。
- 执行清理时,按依赖关系倒序:先
caches.keys().then(keys => keys.forEach(key => caches.delete(key)))清 Cache API,再indexedDB.databases().then(dbs => dbs.forEach(db => indexedDB.deleteDatabase(db.name))),最后才是localStorage.clear()和sessionStorage.clear() - 注意
indexedDB.deleteDatabase()是异步的,得等所有onsuccess触发完才算真正删干净,不能清完就弹“已清除” - 别信“localStorage.clear() 就万事大吉”——有些老框架会在
localStorage里写一堆带时间戳的 key(如form_draft_1712458560),clear 会一锅端,但用户可能只想删某一条草稿,得提供细粒度 remove 能力
配额不是固定数字,而是浏览器、设备、隐私策略共同作用的浮动阈值;真正的适配,不是算准“还能存多少”,而是让系统在任意配额下都能继续运转——这要求每一步写入都有兜底,每一次清理都彻底,每一处大对象都有替代路径。


















