能,但需序列化为纯json数据:撤销栈须剔除dom节点、函数等不可序列化内容,仅保存html字符串、光标位置、时间戳等快照信息,并在还原时通过编辑器api安全恢复。

sessionStorage 能存撤销栈吗?能,但得序列化
直接 sessionStorage.setItem('undoStack', undoStack) 会报错:TypeError: Converting circular structure to JSON。因为撤销栈里通常包含 DOM 节点、函数或循环引用对象(比如编辑器状态里有 editor 实例引用自身),而 sessionStorage 只接受字符串值。
必须先深拷贝 + 序列化为纯数据结构:
- 只保存可序列化的“快照”:比如 HTML 字符串、光标位置
{anchorOffset, focusOffset}、选区范围文本内容,而不是 DOM 节点本身 - 避免存函数、正则、Date、Map/Set 等非 JSON-safe 类型
- 推荐用
JSON.stringify()存,JSON.parse()取;若需保留数字精度或特殊类型,考虑structuredClone()(注意兼容性)
怎么设计一个可存进 sessionStorage 的撤销项结构
撤销项不能依赖运行时上下文。例如 TinyMCE 或自研编辑器,每次执行修改后生成的 undo item 应该是:
{"html": "<p>Hello</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6147" title="Pub Session Logs"><img
src="https://img.php.cn/upload/skill/000/000/081/179082559454093.jpg" alt="Pub Session Logs" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6147" title="Pub Session Logs" class="overflowclass">Pub Session Logs</a>
<p class="overflowclass">使用 jq 搜索和分析您自己的会话日志。还有 50+ 模型用于图像生成、视频生成、语音合成、语音识别、音乐、聊天等</p>
</div>
<a rel="nofollow" href="/xiazai/skill6147" title="Pub Session Logs" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>", "cursor": {"line": 0, "offset": 5}, "timestamp": 1718234567890}
关键点:
-
html字段必须是纯净 HTML 字符串(不含事件监听器、内联 script/style),可用el.innerHTML或new DOMParser().parseFromString(...).documentElement.outerHTML提取 - 光标/选区信息要独立于 DOM 实例,否则还原时无法定位——建议用文本偏移(
textContent下的字符索引)或相对路径(如["p", 0, "childNodes", 0]) - 不要存
document.execCommand历史(已废弃),也不要存 MutationRecord —— 它们不可序列化且浏览器行为不一致
还原撤销栈时如何避免 DOM 污染和状态错位
从 sessionStorage.getItem('undoStack') 拿到字符串后,JSON.parse() 出来的是普通对象,没有原型链和方法。还原到编辑器时容易出问题:
- 直接 innerHTML 赋值可能丢失 editor 实例维护的内部状态(如 undoManager 栈顶指针)——需要手动重置
editor.undoManager.stackIndex - 光标恢复要用
range.setStart(el, offset),但 el 是新创建的节点,旧节点引用已失效;必须基于当前编辑器根容器重新查找匹配位置 - 如果编辑器用了 contenteditable + 自定义 schema(如 Slate、ProseMirror),必须用其内置 API 还原(如
editor.applySnapshot(snapshot)),不能硬塞 HTML
sessionStorage 生命周期和容量限制必须主动应对
sessionStorage 在关闭标签页后自动清空,看似适合“当前会话”,但实际有坑:
- 用户右键「在新标签页打开」编辑页面 → 新标签页无历史栈,因为
sessionStorage不共享 - Chrome 对单个域名限制约 5–10MB,一个含 50 个 HTML 快照(平均 20KB)的栈就接近 1MB;频繁存大 HTML(含 base64 图片)会快速触顶,触发
QuotaExceededError - 建议加保护逻辑:
try { sessionStorage.setItem(...) } catch (e) { if (e.name === 'QuotaExceededError') this.undoStack.shift(); }
真正难处理的不是存或取,而是“什么时候删”。比如用户切换编辑器模式(所见即所得 ↔ 源码)、或加载新文档,必须显式清空 sessionStorage.removeItem('undoStack'),否则残留旧栈会干扰新内容。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










