多标签页通讯方案

使用 localStorage、SharedWorker、BroadcastChannel 实现标签页间通信

问题

如何实现同源网页多标签页(Tab)之间的通讯?

解答

方案一:localStorage + storage 事件

利用 localStorage 变化时触发的 storage 事件实现跨标签页通信。

// 发送消息
function sendMessage(data) {
  localStorage.setItem('message', JSON.stringify({
    data,
    timestamp: Date.now() // 加时间戳确保每次都能触发事件
  }))
}

// 接收消息(其他标签页)
window.addEventListener('storage', (e) => {
  if (e.key === 'message' && e.newValue) {
    const { data } = JSON.parse(e.newValue)
    console.log('收到消息:', data)
  }
})

// 使用
sendMessage({ type: 'login', userId: 123 })

注意storage 事件只在其他标签页触发,当前页面不会触发。

方案二:BroadcastChannel

专门用于同源页面间广播通信的 API,使用最简单。

// 创建频道(所有标签页使用相同的频道名)
const channel = new BroadcastChannel('my-channel')

// 发送消息
channel.postMessage({ type: 'update', content: 'hello' })

// 接收消息
channel.onmessage = (e) => {
  console.log('收到消息:', e.data)
}

// 关闭频道
channel.close()

方案三:SharedWorker

多个标签页共享同一个 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)
      }
    })
  }
}
// 页面中使用
const worker = new SharedWorker('shared-worker.js')

// 发送消息
worker.port.postMessage({ type: 'notify', msg: 'hello' })

// 接收消息
worker.port.onmessage = (e) => {
  console.log('收到消息:', e.data)
}

// 必须调用 start(如果没用 onmessage 而是用 addEventListener)
worker.port.start()

方案对比

方案优点缺点
localStorage兼容性好,简单只能传字符串,有容量限制
BroadcastChannelAPI 简洁,支持任意数据IE 不支持
SharedWorker可做复杂逻辑处理实现复杂,兼容性一般

其他方案

// 方案四:postMessage + window.open
// 适用于父页面打开子页面的场景
const child = window.open('child.html')
child.postMessage('hello', '*')

// 子页面接收
window.addEventListener('message', (e) => {
  console.log(e.data)
})

关键点

  • localStorage 的 storage 事件只在其他标签页触发,当前页面不触发
  • BroadcastChannel 是最简洁的方案,推荐优先使用
  • SharedWorker 适合需要共享状态或复杂逻辑的场景
  • 所有方案都要求同源(协议、域名、端口相同)
  • postMessage 可用于有引用关系的窗口间通信