BroadcastChannel API

深入理解 BroadcastChannel API:同源标签页间的消息广播、跨 iframe 通信、以及与 postMessage 的对比。

BroadcastChannel API

一、BroadcastChannel 概述

1.1 什么是 BroadcastChannel

BroadcastChannel API 允许同源(same-origin)的浏览器上下文(标签页、窗口、iframe、worker)之间进行双向通信。与 postMessage 不同,BroadcastChannel 是发布-订阅模式,发送者不需要知道接收者。

// 创建频道
const channel = new BroadcastChannel('settings');

// 发送消息
channel.postMessage({ type: 'theme', payload: 'dark' });

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

1.2 与其他通信方式对比

API 通信范围 模式 复杂度
postMessage 跨窗口/iframe 点对点 高(需要目标引用)
BroadcastChannel 同源广播 发布-订阅
MessageChannel 双向通道 管道
localStorage 事件 同源标签页 广播 低(仅字符串)

二、核心 API

2.1 创建频道

const channel = new BroadcastChannel('my-channel');

2.2 发送消息

// 发送任何结构化数据
channel.postMessage({
  type: 'update',
  data: { id: 1, value: 'test' }
});

// 发送简单消息
channel.postMessage('Hello from channel');

2.3 接收消息

channel.onmessage = (event) => {
  const { type, data } = event.data;
  console.log('Message received:', type, data);
};

// 也可以使用 addEventListener
channel.addEventListener('message', (event) => {
  console.log(event.data);
});

2.4 关闭频道

channel.close();

三、实际应用

3.1 主题同步

// 页面 1(或其他标签页)
const themeChannel = new BroadcastChannel('theme');

function setTheme(theme) {
  document.documentElement.dataset.theme = theme;
  localStorage.setItem('theme', theme);
  themeChannel.postMessage({ theme });
}

// 监听来自其他标签页的主题变化
themeChannel.onmessage = (e) => {
  if (e.data.type === 'theme') {
    setTheme(e.data.theme);
  }
};

3.2 登录状态同步

// 监听登录状态变化
const authChannel = new BroadcastChannel('auth');

authChannel.onmessage = (e) => {
  if (e.data.type === 'logout') {
    // 清除本地状态并重定向
    localStorage.removeItem('token');
    window.location.href = '/login';
  }
});

// 退出登录时通知其他标签页
function logout() {
  authChannel.postMessage({ type: 'logout' });
  window.location.href = '/login';
}

3.3 购物车同步

const cartChannel = new BroadcastChannel('cart');

// 添加商品
function addToCart(product) {
  cart.push(product);
  localStorage.setItem('cart', JSON.stringify(cart));
  cartChannel.postMessage({
    type: 'cart-update',
    cart: cart
  });
}

// 监听其他标签页的更新
cartChannel.onmessage = (e) => {
  if (e.data.type === 'cart-update') {
    updateCartUI(e.data.cart);
  }
};

四、Worker 中的使用

4.1 Service Worker 广播

// Service Worker
self.addEventListener('message', (event) => {
  if (event.data.type === 'broadcast') {
    const channel = new BroadcastChannel('sw-broadcast');
    channel.postMessage(event.data.payload);
    channel.close();
  }
});

4.2 SharedWorker 广播

// SharedWorker
const connections = new Set();

self.onconnect = (e) => {
  const port = e.ports[0];
  connections.add(port);

  port.onmessage = (event) => {
    // 广播到所有连接
    connections.forEach(p => {
      if (p !== port) {
        p.postMessage(event.data);
      }
    });
  };

  port.start();
};

五、注意事项

5.1 同源限制

// ❌ 不同源的标签页无法通信
// https://example.com 和 http://example.com 无法通信
const channel = new BroadcastChannel('shared');

// ✅ 同源即可,协议、域名、端口都相同
// https://example.com:443 和 https://example.com 可以通信

5.2 消息大小

// 消息会被结构化克隆算法序列化
// 大消息可能影响性能
channel.postMessage({ largeArray: new Array(1000000) }); // ⚠️ 可能卡顿

5.3 浏览器支持

// 检测支持
if ('BroadcastChannel' in window) {
  console.log('BroadcastChannel supported');
}

六、面试高频问题

Q: BroadcastChannel 和 postMessage 有什么区别?

回答要点:postMessage 是点对点通信,需要知道目标窗口/iframe 的引用;BroadcastChannel 是发布-订阅模式,发送者不知道也不关心谁在接收。同源下多个标签页通信 BroadcastChannel 更简单,需要精确指定目标时用 postMessage。

Q: BroadcastChannel 可以跨域通信吗?

回答要点:不可以。BroadcastChannel 遵循同源策略,只有协议、域名、端口都相同的上下文才能通信。跨域通信需要使用 postMessage 并指定 targetOrigin。

Q: BroadcastChannel 适合什么场景?

回答要点:需要通知多个同源标签页的场景:主题切换同步、登录状态同步、购物车更新、缓存失效通知、实时协作应用中的状态同步等。


参考资料

延展阅读