FumadocsZDecode
浏览器

Tab 通信

一、为什么跨 tab 需要特殊处理

每个 tab 运行在独立的渲染进程,内存完全隔离,JS 无法直接访问另一个 tab 的变量,必须通过浏览器提供的跨进程通信机制。


二、同域多账号的隔离问题

Cookie 按域名共享,不按 tab 隔离:

Tab A:登录张三 → 写入 Cookie token=zhangsan
Tab B:登录李四 → 覆盖 Cookie token=lisi

结果:Tab A 刷新后变成李四,因为 Cookie 被覆盖

解决方案:用 sessionStorage 存 token

// sessionStorage 天然按 tab 隔离,互不干扰
sessionStorage.setItem('token', '张三的token')

sessionStorage 在不同 tab 之间不共享,关闭 tab 后自动清除,非常适合多账号场景。


三、跨 tab 通信方案

BroadcastChannel(推荐)

// Tab A — 发送
const channel = new BroadcastChannel('my-app')
channel.postMessage({ user: '张三', action: 'logout' })

// Tab B — 接收(同域名下自动收到)
const channel = new BroadcastChannel('my-app')
channel.onmessage = (e) => {
  console.log(e.data) // { user: '张三', action: 'logout' }
}

localStorage 事件

// Tab A — 写入触发事件
localStorage.setItem(
  'msg',
  JSON.stringify({
    data: 'hello',
    timestamp: Date.now(),
  }),
)

// Tab B — 监听(只有其他 tab 修改才触发,自己修改不触发)
window.addEventListener('storage', (e) => {
  if (e.key === 'msg') {
    console.log(JSON.parse(e.newValue))
  }
})

SharedWorker(共享线程)

所有 tab 共享同一个 worker 实例,worker 作为消息中转站:

// shared-worker.js
const ports = []

self.onconnect = (e) => {
  const port = e.ports[0]
  ports.push(port)

  port.onmessage = (e) => {
    ports.forEach((p) => {
      if (p !== port) p.postMessage(e.data) // 广播给其他 tab
    })
  }
}
// 每个 tab
const worker = new SharedWorker('shared-worker.js')
worker.port.start()

worker.port.postMessage({ action: 'updated' })

worker.port.onmessage = (e) => {
  console.log('其他 tab 说:', e.data)
}

Service Worker

// sw.js
self.addEventListener('message', (e) => {
  self.clients.matchAll().then((clients) => {
    clients.forEach((client) => {
      if (client.id !== e.source.id) {
        client.postMessage(e.data)
      }
    })
  })
})
// tab 里
navigator.serviceWorker.controller.postMessage({ action: 'sync' })

navigator.serviceWorker.onmessage = (e) => {
  console.log(e.data)
}

四、方案对比

方案实现难度实时性兼容性适合场景
BroadcastChannel最简单实时现代浏览器通用跨 tab 通信
localStorage 事件简单近实时最好简单状态同步
SharedWorker中等实时现代浏览器需要共享状态 / 连接
Service Worker较复杂实时现代浏览器PWA、离线场景

五、实战:多账号 + 退出通知

// Tab A(张三)退出,通知其他 tab
const channel = new BroadcastChannel('auth')

function logout() {
  sessionStorage.removeItem('token') // 只清自己的 sessionStorage
  channel.postMessage({ type: 'logout', account: '张三' })
}

// Tab B(李四)收到通知
const channel = new BroadcastChannel('auth')
channel.onmessage = (e) => {
  if (e.data.type === 'logout') {
    console.log(`${e.data.account} 退出了`)
    // 李四的 token 在自己的 sessionStorage,不受影响
  }
}

六、一句话总结

跨 tab 通信首选 BroadcastChannel,简单直接。多账号隔离用 sessionStorage 存 token(天然 tab 隔离),不要依赖 Cookie。

On this page