input事件配合800ms防抖是保存草稿最可靠方式,blur不可靠因触发时机不确定;beforeunload仅作同步兜底;恢复时需dom就位、触发变更事件,并校验时间戳防旧稿覆盖。

blur事件保存草稿为什么总丢数据
直接用 onblur 或 addEventListener('blur', ...) 存 localStorage 是最常见但最不可靠的路径。用户还没点提交就切走、关页、按 F5 刷新,blur 根本来不及触发——尤其在移动端,键盘收起不保证触发失焦,iOS Safari 里 contenteditable 元素甚至完全不派发 blur。更隐蔽的是:中文输入法下,“上海”可能先存了“shang”,再存“shanghai”,最后才上屏,而 blur 捕获的往往是中间态。
input + 防抖才是实际可用的主通道
必须监听 input 事件,并叠加防抖(debounce),800ms 是当前实测最稳的阈值:
- 太短(如 300ms):拼音选词未完成,存入“zhongg”而非“中国”
- 太长(如 2s):用户写完停顿 1.5 秒关页,最后一版就丢了
- 防抖必须手写,避免引入 Lodash 等依赖带来的执行不确定性
- 对
type="checkbox"和type="radio",要读el.checked,不是el.value
示例关键逻辑:
let saveTimer;<br>form.addEventListener('input', (e) => {<br> const el = e.target;<br> if (el.matches('input, textarea, select') && !el.hasAttribute('data-no-autosave')) {<br> clearTimeout(saveTimer);<br> saveTimer = setTimeout(() => {<br> const key = `form-draft-${form.id}-${el.name}`;<br> const value = el.type === 'checkbox' || el.type === 'radio'<br> ? el.checked<br> : el.value.trim() || null;<br> if (value != null) localStorage.setItem(key, JSON.stringify(value));<br> }, 800);<br> }<br>});
beforeunload 只能同步写,且仅作兜底
beforeunload 不是主保存时机,而是最后一道同步保险:
- 不能在里面调异步函数、不能
await、不能fetch,浏览器只给约 50ms 同步执行窗口 - 不要封装成
saveDraft()再调用——万一它内部有 try/catch 或 await,就失效了 - 只做一件事:
localStorage.setItem(...),且只存已标记为“脏”的字段(用全局isDirty = true控制) - 测试时注意:
location.reload()不触发beforeunload,必须真实关闭标签页验证
恢复草稿时 DOM 必须真就位,且要触发变更事件
在 DOMContentLoaded 里遍历还原,大概率失败——React/Vue 组件还没挂载,Shadow DOM 还没 attach,填了 value 也没用:
- 对原生表单:等
form.offsetParent !== null或用requestIdleCallback延后执行 - 还原
checkbox时,写el.checked = JSON.parse(stored) === true,别信Boolean('false') - 还原后手动派发
input或change事件,让框架感知状态变化 - 富文本编辑器(如 Quill)必须调
editor.setContents(),不能直接改innerText
最易被忽略的其实是时间戳校验:多标签页同时编辑同一表单时,localStorage 无并发控制,旧草稿覆盖新提交是常态。加一个 form-draft-${id}-timestamp 键,还原前比对是否超 60 分钟,否则弹提示而不是静默覆盖。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











