sharedworker 不能直接读写 localstorage 或发起 fetch,但可作为调度中枢实现多标签页内存缓存共享与请求去重;需用 map 存带 ttl 的原始 js 值,通过 pendingrequests 实现请求合并,监听 port.onclose 清理引用,并为 safari 提供 broadcastchannel + localstorage 降级方案。

SharedWorker 本身不能直接读写 localStorage 或发起网络请求(如 fetch),但它能作为调度中枢,把缓存逻辑收口到一个线程里,让多个标签页共用同一份内存缓存 + 统一去重请求。关键不是“它缓存了什么”,而是“它阻止了多少重复请求”。
SharedWorker 里怎么存缓存数据?别碰全局变量
SharedWorker 脚本运行在独立上下文,self 下可以声明普通对象或 Map,但必须注意:所有页面共享的是同一个实例,所以缓存结构要可并发访问、不依赖页面生命周期。
- 用
Map存键值对比Object更安全,避免原型污染和隐式类型转换(比如cache.set('1', 'a')和cache.set(1, 'b')是两个不同 key) - 不要用
JSON.stringify()后存字符串——序列化/反序列化开销大,且丢失 Date、Map、Set 等类型;缓存原始 JS 值即可 - 缓存条目需带 TTL(时间戳),每次 get 前检查是否过期;不建议依赖
setTimeout清理,因为 SharedWorker 可能长期空闲,定时器不会触发 - 示例:
const cache = new Map(); // key: string, value: { data, expiresAt }
怎么让多个标签页的 fetch 请求不重复发?靠请求去重队列
页面 A 和 B 同时调用 get('/api/user/123'),SharedWorker 必须只发一次网络请求,再把结果分发给两者。这需要“请求合并”而非“简单缓存查表”。
- 收到请求时,先检查
cache.has(key);命中则直接回传,不进队列 - 未命中时,生成唯一
requestId(如Date.now() + Math.random()),并以 URL 为 key 存入pendingRequests.set(url, { requestId, resolve, reject, ports: [port] }) - 若同一 URL 已在 pending 中,不发新请求,只把当前
port推入ports数组 - 请求返回后,遍历
ports逐个port.postMessage({ requestId, data }),再从cache和pendingRequests中清理 - 注意:
fetch必须在 SharedWorker 内发起,页面侧只传参数,不传AbortController—— 它无法跨线程传递
页面卸载时如何避免缓存失效或请求悬空?主动 close port 并清理
用户关掉某个标签页,其对应的 port 会断开,但 SharedWorker 若还拿着这个 port 引用并尝试 postMessage,会静默失败,甚至卡住整个响应流程。
- 页面侧要在
beforeunload中显式调用worker.port.close(),触发 SharedWorker 的port.onclose - SharedWorker 中监听
port.onclose = () => { pendingRequests.forEach(req => req.ports = req.ports.filter(p => p !== port)) } - 每个 pending 请求必须设超时(如 10 秒),超时后调用所有
resolve/reject并清空该条目,否则队列会越积越多 - 不建议用
self.onoffline判断网络中断——它只反映浏览器整体离线状态,不是连接粒度的
为什么 Safari 上容易 fallback?它的 SharedWorker 支持不完整
Safari 自 15.4 起才支持 SharedWorker,且存在两个硬伤:不支持 SharedWorker 构造函数的 name 选项;port.start() 行为不稳定,有时需手动调用两次才能激活消息通道。
- 检测兼容性必须用
typeof SharedWorker === 'function',不能只靠 try/catch - 降级方案不是“不用缓存”,而是退回到页面级内存缓存 +
localStorage时间戳比对(例如存__cache_user_123_ts) - 如果项目必须支持旧 Safari,建议用
BroadcastChannel搭配localStorage事件做轻量同步,虽然有延迟,但比完全无共享强 - 不要在 SharedWorker 里调用
indexedDB.open——Safari 对 SharedWorker 中的 IndexedDB 支持极差,容易报InvalidStateError
真正难的不是写通 SharedWorker,而是设计好请求生命周期的边界:什么时候算“一个请求结束”,什么时候该“广播状态变更”,以及哪个环节出错会导致整个队列卡死。这些细节不写进代码注释里,半年后你自己都看不懂。











