data-sync 本身不共享状态,仅作为映射桥梁,跨页面同步需结合 localStorage 和 storage 事件实现;SharedWorker 才能真正共享运行时状态,但兼容性有限,须配合 localStorage 降级方案。

data-sync 属性本身不共享状态,只是映射桥梁
HTML 原生属性(如 checked、value)只在当前页面生效,刷新或新开标签页后就丢失。所谓“跨页面组件状态共享”,实际是靠 data-sync + localStorage + storage 事件组合实现的——data-sync 只是告诉 JS “这个元素该读哪个 key”,它自己不广播、不持久、不响应变化。
常见错误现象:
- 页面 A 勾选了
<input type="checkbox" data-sync="theme-dark">,页面 B 仍是未勾选状态 - 表单输入框写了半截关掉页面,再打开另一个同路径页面,内容为空
- 多个标签页同时操作同一组开关,只有当前页 DOM 更新,其他页无反应
根本原因:DOM 属性不是状态源,只是渲染快照。真正唯一可信的状态源必须是 localStorage。
用 data-sync + localStorage 实现自动绑定
给需要同步的元素加上 data-sync 属性,值为 localStorage 的键名,再统一初始化和监听,就能让所有同源页面自动对齐。
立即学习“前端免费学习笔记(深入)”;
示例 HTML(两页都写):
<input type="checkbox" id="theme-dark" data-sync="pref-theme-dark"> <input type="text" id="user-name" data-sync="form-username">
共用 JS(每个页面都执行):
// 初始化:从 localStorage 读值并写入对应属性
document.querySelectorAll('[data-sync]').forEach(el => {
const key = el.getAttribute('data-sync');
const saved = localStorage.getItem(key);
if (saved !== null) {
if (el.type === 'checkbox') {
el.checked = saved === 'true';
} else {
el.value = saved;
}
}
});
// 监听用户输入并写回 localStorage
document.addEventListener('input', e => {
const el = e.target;
const key = el.getAttribute('data-sync');
if (key) {
const value = el.type === 'checkbox' ? el.checked.toString() : el.value;
localStorage.setItem(key, value);
}
});
// 监听 storage 事件,更新其他页面的 DOM
window.addEventListener('storage', e => {
document.querySelectorAll(`[data-sync="${e.key}"]`).forEach(el => {
if (e.newValue !== null) {
if (el.type === 'checkbox') {
el.checked = e.newValue === 'true';
} else {
el.value = e.newValue;
}
}
});
});
注意点:
-
storage事件只在「其他页面」触发,当前页修改localStorage不会收到自己的事件,所以必须手动初始化 + 监听 input - checkbox 的值要存字符串
'true'/'false',不能直接存布尔值(localStorage只存字符串) - 避免高频写入:对
input事件做防抖(如 300ms),否则快速打字可能触发几十次setItem
SharedWorker 是唯一能真正复用状态的方案
如果组件状态依赖实时连接(如 WebSocket、心跳、订阅关系),仅靠 localStorage 不够——它只能存快照,无法共享运行时实例。这时必须用 SharedWorker。
它让所有同源页面共用一个后台线程,WebSocket 实例、定时器、变量全部共享。页面之间不是“通知对方改状态”,而是“访问同一个状态”。
关键逻辑在 Worker 内:
- 声明
let ws = null和let connectionState = 'closed',所有页面通过postMessage请求状态,得到的是同一份内存值 - 页面发送
{ type: 'send', data: {...} },Worker 统一转发;服务端返回后,Worker 按clientId精准投递 - 页面关闭前发
{ type: 'unregister' },Worker 清理对应端口状态;异常断开则靠self.clients.matchAll()定期核对
限制很实在:
- 仅 HTTPS 或
localhost可用 - Safari 直到 16.4+ 才稳定支持,iOS 仍有限制
- 必须同源且 URL 完全一致(含端口),否则新建一个 Worker 实例
兼容性 fallback 必须写进主流程
生产环境不能假设 SharedWorker 一定可用,也不能指望用户都用新 Safari。降级链路得是默认路径的一部分。
检测与切换逻辑示例:
if (typeof SharedWorker !== 'undefined') {
try {
worker = new SharedWorker('cart-worker.js');
worker.port.start();
} catch (e) {
useLocalStorageFallback();
}
} else {
useLocalStorageFallback();
}
useLocalStorageFallback() 不是简单监听 storage 事件——它还要模拟“连接状态”:
- 用
localStorage.setItem('ws_status', 'connected')模拟连接成功 - 各页面轮询检查该 key,超时未更新则视为断连,触发重连逻辑
- 消息投递改用
postMessage+window.open引用,或退化为纯本地状态同步
最容易被忽略的一点:所有状态变更逻辑(比如“用户登出后清空购物车”)必须在 Worker 或 fallback 主控页里执行,不能分散在各个页面里——否则竞态、漏清、重复清就会立刻出现。



















