浏览器跨标签页通信方案

应用场景:

  • 状态同步:多标签页之间同步数据,比如同步设备展示状态,同步数据信息。
  • 消息通知:通知其余标签页执行动作,比如说跳转其他页面,完成后,通知打开页面执行状态变更或刷新灯操作。
  • 隐私数据通信:基于加壳客户端,实现 H5 页面之间的本地通信机制,不经过网络层传输,还根据随机加密值实现跨权限式加密信道。

实现场景:

  • 需要跨域:Websocket、父子窗口。
  • 无需跨域:LocalStorage、SharedWorker、BroadcastChannel。

LocalStroage

利用同域下 LocalStorage 共享策略。

  • 实现跨标签页的信息同步。
  • 实现简单。

存在问题:

  • LocalStorage 有 5MB 大小限制,需要注意消息限制问题。

使用:

  1. A 页面监听 storage 事件。
  2. B 页面通过调用 LocalStorage 方法,触发事件,传给 A 页面进行使用
    • 通过获取 e.newValue 变化即可得知当前的传送的消息。
    • 当执行 removeItem 时,e.newValue 会变为 null。
    • 通过 e.url 判断消息是否为自己发出,避免重复处理,或者监听消息来源。
// A 页面
window.addEventListener('storage',(e)=>{
    console.log(e.key) // key
    console.log(e.oldValue) // 旧值
    console.log(e.newValue) // 新值,即设置的值
    console.log(e.storageArea) // 被操作的 storage 对象
    console.log(e.url) // 文档改变的对象地址来源
})


//  B 页面
window.LocalStorage.setItem('xx','xxx')
window.LocalStorage.removeItem('xxx','xxx')	

SharedWorker

基于共享线程来完成通信,是独立于主线程的后台共享线程。
SharedWorker 本身承载业务共享逻辑,底层通信手段基于 MessagePort 实现。

  • 通过 port 通信降低主线程和 Shared Worker 的耦合度。
  • 生命周期和连接的主线程相关,主线程全部释放,SharedWorker 也会终止(可能会等待异步任务执行完成,但是每测试出来)。
  • 可能异常情况:
    • SecurityError :不能正常启动 Shraed Worker。
    • NetworkError :Shared Worker 不是 application/json 格式。
    • SyntaxError :URL 无法解析。

存在问题:

  • 兼容性问题,很多移动端浏览器不支持。
  • 增加请求和维护成本,定义额外的 js 文件。
  • 调试困难,需要通过 chrome://inspect#workers 界面查看调试信息。
    shared worker 调试界面,点击 inspect 查看调试信息

使用:

主线程:

  1. 连接 SharedWorker。
  2. 通过 shareWorker.port.postMessage 向所有连接的页面发送消息。
  3. 通过 shareWorker.port.onmessage 接收发来的消息。
// 页面内(主线程)
const shareWorker = new ShareWorker('share-worker.js')
shareWorker.port.onmessage = (e)  => {
  const { type, data } = e.data
  if(type === 'BROADCAST'){
    // 处理逻辑
  }
}
// 处理连接错误
worker.port.onerror = function (error) {
  console.error("Shared Worker 错误:", error);
};


// 显示激活
shareWorker.port.start()


function sendMessage(){
  shareWorker.port.postMessage({type:'MESSAGE',data:{...}})
}

function disconnect(){
  shareWorker.port.postMessage({type:'DISCONNECT'})
  shareWorker.port.close()
}

shared worker文件:

  1. 通过 self.onconnect 监听连接事件,只要完成初始化就会触发。
  2. 使用 Set 集合 connections存储端口,支持 O(1) 操作消耗。
  3. 操作:
    • 通过 port.onmessage 监听任一主线程发来的消息,遍历 connections 广播消息。
    • 在使用完成时,通过 port.close 关闭端口并清除端口在 connections 中的缓存,一定要清除连接,避免出现缓存端口导致广播无效端口的情况出现。
    • port.start 用于触发端口激活,调用不会触发什么内容,但是部分浏览器不调用可能会阻塞消息发送。
// share-worker.js (share Worker线程)

// 通过 connections 管理端口
const connections = new Set();

// 消息广播
function broadcast(){
  connections.forEach(p => {
    // 排除自身
    if (p !== port) { 
      p.postMessage({ type: 'BROADCAST', data: `${data}` });
    }
  });
}

function disconnect(port){
  connections.delete(port);
  port.close(); // 关闭端口
}



self.onconnect = (e) => {
  // 1. 读取端口:自动去重
  const port = e.ports[0];
  connections.add(port);

  // 2. 监听消息
  port.onmessage = (e) => {
    const { type, data } = e.data;
    switch (type) {
      case 'MESSAGE':
        console.log('收到主线程消息:', data);
        // 广播给所有连接的主线程
        broadcast(data,port)
        break;
      case 'DISCONNECT':
        disconnect(port)
        break;
    }
  };

  // 3. 监听端口错误/关闭,自动清理无效端口
  port.onerror = (err) => {
    console.log('端口错误:', err);
    connections.delete(port);
  };

  // 显示激活端口(部分浏览器需显式调用)
  port.start();
};

Brocastchannel

支持同源下跨标签页通信方案,适用于广播消息方案。

  • 创建频道名称,只要在同个频道名称下的标签,都能接收广播消息。
  • 无需像 SharedWorker 那样遍历广播,直接发送即可。
  • 实现简单,几行代码即可搞定。

存在问题:

  • 2022 年才稳定的 API ,需要考虑下兼容性问题。
  • 无法控制广播域,需要通过多频道来区分广播形式,或者通过标识符判断要处理消息的页面。

使用:

  • 通过 new BroadcastChannel(channelName) 创建频道。
  • 通过监听 message 来监听广播消息。
  • 通过 channel.postMessage() 发送消息。
const broadcastChannel = new BroadcastChannel("channel");
// 接收其他同频道的广播消息
broadcastChannel.addEventListener("message", (e) => {
  const { type,data } = e.data
});
// 为同频道广播消息
broadcastChannle.postMessage({type:'message',data})

父子窗口

当使用 window.open() 打开窗口,可以通过父子窗口执行通信。

  • 利用 **浏览器窗口句柄(Window Handle)**和消息传递 支持了父子通信。
  • 可以使用父窗口作为中间层,实现各个子窗口的流通性以及广播功能。
  • 兼容性好。

存在问题:

  • 需要窗口之间存在父子引用关系。
  • 需要额外维护子窗口的引用信息。

使用:

  1. 父窗口通过 window.open 打开子窗口,同时保留子窗口的引用。
  2. 基于 window.name 区分子窗口,便于后续跨子窗口通信。
  3. 监听各自 windowmessage 事件实现通信 。
  4. 父窗口通过 child.postMessage() 发送消息,子窗口通过 window.opener.postMessage() 回复消息,如果要跨域,可以通过 postMessage 第二参数指定目标源,* 代表向所有源发送消息。
// 父窗口
const child = window.open('/some-page')
// 给子窗口定义唯一id,便于通信
child.name = 'win_' + id() 
window.addEventlistener('message',()=>{
  // 监听子窗口传递的消息
})

// 在子窗口 /some-page 页面
// 发送数据到父窗口
window.opener.postMessage({type:'message', name:window.name, data})

window.addEventlistener('message',()=>{
  // 监听父窗口传递的消息
})

Websocket

通过 websocket ,以服务端做中转,实现跨域通信方案。

  • 支持跨域,同时支持跨浏览器跨设备。
  • 可以结合其他跨标签方案,实现复用 websocket 连接的操作。

存在问题

  • 需要服务端支持,实现较为复杂。

MessageChannel

这个方案还是比较常用在 iframe 跨域通信机制中,仅记录。

MessageChannel 本质也是依赖 MessagePort 来实现的通信机制,本质和 SharedWorker 一样。
如果实现跨标签页通讯,需要有中间人且支持结构化克隆算法来协助传递 port 端口信息。

参考内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值