dom快照不能只存outerhtml,否则表单值和滚动位置全丢;必须用clonenode(true)深拷贝业务容器并手动补全.value、.checked、.selectedindex及scrolltop等运行时状态,回滚时通过状态注入而非结构重载。

DOM快照不能只存outerHTML,否则表单值和滚动位置全丢
直接用 document.documentElement.outerHTML 保存快照,等于只记了“图纸”,没记“当前工人干到哪一步”。input.value、textarea.value、select.selectedIndex、scrollLeft、scrollTop 这些运行时状态根本不会出现在 HTML 字符串里。Canvas 像素、WebGL 上下文、已绑定的事件监听器,也一并清空。
实操建议:
- 只对业务容器快照,比如
document.getElementById('app'),别整棵 DOM 树 - 用
cloneNode(true)深拷贝节点树,再遍历所有input、textarea、select,手动补上.value、.checked、.selectedIndex - 记录滚动位置时,优先读取容器自身的
scrollTop,而不是window.scrollY——尤其当容器是自定义滚动区域时 - 含 Canvas 的场景,必须调
canvas.toDataURL()或ctx.getImageData();注意跨域图片会触发SecurityError
回滚不是 replaceChild,而是状态注入
用 container.innerHTML = snapshotHTML 或 container.replaceChild(newClone, old) 回滚,会销毁 Vue/React 组件实例、清空事件监听器、中断定时器,轻则白屏,重则交互完全失效。
实操建议:
- Vue/React 应用:快照应转为纯 JS 对象(如
{ form: { name: 'Alice' }, scroll: 420 }),回滚时触发响应式更新,而非操作 DOM - 纯 JS 控制的表单:用
Object.assign(el, savedProps)恢复属性,别反复 setAttribute ——el.checked = true和el.setAttribute('checked', '')行为不等价 - 滚动恢复必须等 DOM 就绪:
requestAnimationFrame(() => el.scrollTop = savedScroll),否则骨架屏或懒加载内容可能覆盖它 - 第三方脚本(如统计 SDK)不能重复执行,快照中需记录已加载的
script.src列表,回滚时跳过
data-* 属性不是状态源,hidden 和 class 才是可靠输出通道
data-user-id 或 data-filters 只适合传初始化参数。JS 改了 setAttribute('data-id', '500'),组件内部不会感知;SSR 渲染的 data-count="10" 被客户端读成字符串,10 + 1 === "101" 就这么来的。
实操建议:
- 结构化数据一律用
JSON.parse(el.getAttribute('data-config')) || {},且必须包try/catch - 状态输出走
el.hidden = true/false(不是setAttribute('hidden', ''))或el.classList.toggle('loading') - 多状态 UI(loading/success/error)用 class 控制,配合命名空间化 CSS 规则,如
.state-loading .spinner - 真要监听 data- 变更,只能走自定义元素 +
attributeChangedCallback,别硬套在普通 div 上
表单撤销/重做必须维护独立状态栈,不能依赖 history API
浏览器 history.back() 对表单无效——它只跳页面,不还原 input 值。用户填一半关掉页面,再打开还是空的;连续改两次,想撤回第一步,却只能全退。
实操建议:
- 表单加载完成时,立即用
Array.from(form.elements)获取静态节点列表(不是form.children,后者是实时 HTMLCollection) - 每次输入后,深拷贝当前所有字段值(
input.value、select.value、checkbox.checked),推入栈;撤销就 pop,重做就 shift - localStorage 存草稿时,用
JSON.stringify({ timestamp: Date.now(), data: formState }),别只存裸对象——时间戳能帮你过滤过期草稿 - 防抖保存间隔设为 800ms,太短压垮主线程,太长用户关页就丢数据
回滚逻辑最易被忽略的点,是“谁负责清理副作用”。快照里存了定时器 ID?回滚时得 clearTimeout;绑了 resize 监听?得先 removeEventListener。状态同步不是复制粘贴,是把运行时上下文完整迁移。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











