题目与场景
你负责多步骤申请表。用户会切换标签页、锁屏、返回上一页,或离开页面数小时。浏览器可能冻结页面、从 bfcache 恢复,或因内存压力丢弃页面。表单需要恢复草稿,且不能覆盖更新的服务端数据。
假设表单包含敏感但非受监管数据,用户可能在多台设备登录,网络也可能间歇性中断。回答必须说明哪些是尽力而为,哪些才是权威状态。
面试官考察什么
- 能否区分可见性、冻结、页面丢弃和 bfcache 恢复。
- 是否避免把
unload或beforeunload当作可靠持久化钩子。 - 本地草稿是否有归属、版本、过期和冲突规则。
- 恢复时能否保留用户意图,不静默替换更新的服务端数据。
作答前的澄清问题
- 丢失本地草稿是否可以接受,还是必须提供持久恢复保证?这决定本地存储是便利功能还是缓存。
- 同一草稿能否在多台设备编辑?如果可以,服务端需要版本或冲突策略。
- 数据是否适合本地保存?敏感字段可能需要加密、选择性省略或完全不持久化。
- 提交合同是什么?草稿保存与最终提交需要不同的幂等和校验规则。
30 秒回答框架
“我把服务端作为权威状态,把本地草稿作为有界恢复缓存。输入变化经过防抖后保存,文档变为 hidden 时再尽力刷新服务端。用 pageshow 在 bfcache 恢复后重新校验,用 resume 在冻结后刷新过期数据。我不依赖 unload。草稿记录过期时间、Schema 版本、服务端基线版本和脏字段;发生冲突时明确展示或合并,不静默覆盖。”
分步骤深入解答
1. 建模生命周期状态
visibilitychange 表示页面变为隐藏或可见,但不保证 JavaScript 会继续运行。浏览器可能冻结隐藏页面,被丢弃的页面甚至不会执行清理。pagehide 和 pageshow 描述导航与 bfcache 恢复,freeze 和 resume 暴露 Chromium 的生命周期转换。
因此应在风险到来前保存,而不是等最后一刻的 unload。文档变为 hidden 时安排小型本地写入和尽力网络刷新;pagehide 时停止非必要工作并记录检查点;pageshow 时检查 event.persisted,在展示恢复表单前重新验证服务端版本。
2. 让本地草稿有界且带版本
只保存适合恢复的字段。草稿记录可以包含:
draft_id, user_id, form_schema, base_server_version,
changed_at, expires_at, dirty_fields, values结构化数据使用 IndexedDB,并用小型本地索引查找。写入要防抖,避免每次输入都写一次;限制载荷大小并删除过期草稿。Schema 版本让应用可以迁移或丢弃不兼容记录,而不是把旧记录当成当前格式解析。
本地记录不是权威状态。它是设备上的缓存,可能缺失、过期、重复,或被浏览器删除。
3. 页面隐藏时安全刷新
文档隐藏时先提交本地检查点,再尝试小型的受认证网络请求。请求携带草稿 ID 和基于哪个服务端版本产生。只有版本匹配时服务端才接受,并返回新版本。
不要让页面等待长请求后才能导航。请求无法完成时,本地检查点仍能保护设备上的内容。避免同步 unload 请求,它会损害导航体验,也不能覆盖移动生命周期路径。
4. 处理 bfcache 与冻结后的恢复
当 pageshow 带有 persisted 时,页面可能在视觉上完整、逻辑上却已经过期。重新获取服务端版本,与表单基线比较;如果另一台设备改过草稿,就显示冲突选择。resume 触发时刷新页面冻结期间可能过期的会话和数据。
被丢弃的页面没有可恢复的内存状态。下次加载时查找未过期本地草稿,把它的基线版本与服务端比较,并提供恢复、丢弃或合并选项。合并前要让用户看到哪些值更新了。
5. 测试失败路径和隐私
测试切换标签页、移动应用切换、前进后退、bfcache 恢复、浏览器丢弃、离线编辑、配额耗尽、Schema 迁移、草稿过期和双设备冲突。必须断言较新的服务端版本不会被静默覆盖。
日志中要脱敏草稿字段。删除账户时清理草稿,设置短保留期,并在隐私说明中解释本地恢复。“已保存”提示只能代表已确认的检查点,不能代表 unload 处理器可能运行过。监控恢复成功率、冲突率、本地写入失败和服务端保存延迟。
高质量示范回答
我会让服务端版本化并作为权威来源,本地只保存有界恢复草稿。输入变化经过防抖后写入 IndexedDB,记录 Schema 版本、过期时间、脏字段集合和服务端基线。visibilitychange 变为 hidden 时写检查点并尝试小型认证保存,但不阻塞导航,也不依赖 unload。
pageshow 触发时,尤其是从 bfcache 恢复时,先重新获取服务端版本再信任恢复表单。resume 时刷新冻结期间可能过期的数据与会话。页面被丢弃后从新加载开始,提供未过期本地草稿。版本不一致时显示冲突界面或逐字段合并,绝不静默覆盖更新的服务端副本。测试覆盖移动端丢弃、离线、配额限制和双设备冲突。
常见失误
- 错误表现:只在
beforeunload保存 → 失败原因:移动浏览器可能不触发,而且会阻碍 bfcache → 修正方法:在输入变化和 hidden 转换时创建检查点。 - 错误表现:把可见的 bfcache 页面当成最新 → 失败原因:快照可能含有过期服务端状态 → 修正方法:在
pageshow重新校验并比较版本。 - 错误表现:把本地存储当成权威状态 → 失败原因:它可能被清理、过期或不可用 → 修正方法:以服务端版本为准,把本地数据呈现为恢复候选。
- 错误表现:在更新版本上重试整个表单 → 失败原因:会覆盖无关编辑 → 修正方法:携带乐观版本只发送脏字段并解决冲突。
- 错误表现:为了调试记录草稿值 → 失败原因:敏感内容会泄露到遥测 → 修正方法:只记录 ID、版本、大小和结果。
追问与回答
应该用 localStorage 还是 IndexedDB?
localStorage 只适合很小的同步元数据。结构化草稿、较大载荷和异步写入使用 IndexedDB。两者都不提供持久保证或跨设备权威性,都需要过期、配额、Schema 版本和隐私规则。
如果用户在两台设备离线编辑怎么办?
每次保存携带服务端基线版本与设备或草稿 ID。恢复联网时只接受版本匹配的写入,然后显示逐字段合并或让用户选择。只有产品明确接受静默丢失且字段相互独立时,才可使用最后写入胜出。
Service Worker 能保证最后一次保存吗?
不能。Service Worker 可以提高重试投递率,但可能被停止、没有网络,或在某条流程中不可用。界面必须显示已确认的检查点,服务端必须让重试幂等。本地草稿仍是请求无法完成时的设备兜底。