瀏覽器跨標(biāo)簽頁(yè)通信方案

應(yīng)用場(chǎng)景:

  • 狀態(tài)同步:多標(biāo)簽頁(yè)之間同步數(shù)據(jù),比如同步設(shè)備展示狀態(tài),同步數(shù)據(jù)信息
  • 消息通知:通知其余標(biāo)簽頁(yè)執(zhí)行動(dòng)作,比如說(shuō)跳轉(zhuǎn)其他頁(yè)面,完成后,通知打開(kāi)頁(yè)面執(zhí)行狀態(tài)變更或刷新燈操作
  • 隱私數(shù)據(jù)通信:基于加殼客戶端,實(shí)現(xiàn) H5 頁(yè)面之間的本地通信機(jī)制,不經(jīng)過(guò)網(wǎng)絡(luò)層傳輸,還根據(jù)隨機(jī)加密值實(shí)現(xiàn)跨權(quán)限式加密信道

實(shí)現(xiàn)場(chǎng)景:

  • 需要跨域:Websocket、父子窗口
  • 無(wú)需跨域:LocalStorage、SharedWorker、BroadcastChannel

LocalStroage

利用同域下 LocalStorage 共享策略

  • 實(shí)現(xiàn)跨標(biāo)簽頁(yè)的信息同步
  • 實(shí)現(xiàn)簡(jiǎn)單

存在問(wèn)題:

  • LocalStorage 有 5MB 大小限制,需要注意消息限制問(wèn)題

使用:

  1. A 頁(yè)面監(jiān)聽(tīng) storage 事件
  2. B 頁(yè)面通過(guò)調(diào)用 LocalStorage 方法,觸發(fā)事件,傳給 A 頁(yè)面進(jìn)行使用
    • 通過(guò)獲取 e.newValue 變化即可得知當(dāng)前的傳送的消息
    • 當(dāng)執(zhí)行 removeItem 時(shí),e.newValue 會(huì)變?yōu)?null
    • 通過(guò) e.url 判斷消息是否為自己發(fā)出,避免重復(fù)處理,或者監(jiān)聽(tīng)消息來(lái)源
// A 頁(yè)面
window.addEventListener('storage',(e)=>{
    console.log(e.key) // key
    console.log(e.oldValue) // 舊值
    console.log(e.newValue) // 新值,即設(shè)置的值
    console.log(e.storageArea) // 被操作的 storage 對(duì)象
    console.log(e.url) // 文檔改變的對(duì)象地址來(lái)源
})


//  B 頁(yè)面
window.LocalStorage.setItem('xx','xxx')
window.LocalStorage.removeItem('xxx','xxx') 

SharedWorker

基于共享線程來(lái)完成通信,是獨(dú)立于主線程的后臺(tái)共享線程
SharedWorker 本身承載業(yè)務(wù)共享邏輯,底層通信手段基于 MessagePort 實(shí)現(xiàn)

  • 通過(guò) port 通信降低主線程和 Shared Worker 的耦合度
  • 生命周期和連接的主線程相關(guān),主線程全部釋放,SharedWorker 也會(huì)終止(可能會(huì)等待異步任務(wù)執(zhí)行完成,但是每測(cè)試出來(lái))
  • 可能異常情況:
    • SecurityError :不能正常啟動(dòng) Shraed Worker
    • NetworkError :Shared Worker 不是 application/json 格式
    • SyntaxError :URL 無(wú)法解析

存在問(wèn)題:

  • 兼容性問(wèn)題,很多移動(dòng)端瀏覽器不支持
  • 增加請(qǐng)求和維護(hù)成本,定義額外的 js 文件
  • 調(diào)試?yán)щy,需要通過(guò) chrome://inspect#workers 界面查看調(diào)試信息
    shared worker 調(diào)試界面,點(diǎn)擊 inspect 查看調(diào)試信息

使用:

主線程:

  1. 連接 SharedWorker
  2. 通過(guò) shareWorker.port.postMessage 向所有連接的頁(yè)面發(fā)送消息
  3. 通過(guò) shareWorker.port.onmessage 接收發(fā)來(lái)的消息
// 頁(yè)面內(nèi)(主線程)
const shareWorker = new ShareWorker('share-worker.js')
shareWorker.port.onmessage = (e)  => {
  const { type, data } = e.data
  if(type === 'BROADCAST'){
    // 處理邏輯
  }
}
// 處理連接錯(cuò)誤
worker.port.onerror = function (error) {
  console.error("Shared Worker 錯(cuò)誤:", 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. 通過(guò) self.onconnect 監(jiān)聽(tīng)連接事件,只要完成初始化就會(huì)觸發(fā)
  2. 使用 Set 集合 connections存儲(chǔ)端口,支持 O(1) 操作消耗
  3. 操作:
    • 通過(guò) port.onmessage 監(jiān)聽(tīng)任一主線程發(fā)來(lái)的消息,遍歷 connections 廣播消息。
    • 在使用完成時(shí),通過(guò) port.close 關(guān)閉端口并清除端口在 connections 中的緩存,一定要清除連接,避免出現(xiàn)緩存端口導(dǎo)致廣播無(wú)效端口的情況出現(xiàn)。
    • port.start 用于觸發(fā)端口激活,調(diào)用不會(huì)觸發(fā)什么內(nèi)容,但是部分瀏覽器不調(diào)用可能會(huì)阻塞消息發(fā)送。
// share-worker.js (share Worker線程)

// 通過(guò) 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(); // 關(guān)閉端口
}



self.onconnect = (e) => {
  // 1. 讀取端口:自動(dòng)去重
  const port = e.ports[0];
  connections.add(port);

  // 2. 監(jiān)聽(tīng)消息
  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. 監(jiān)聽(tīng)端口錯(cuò)誤/關(guān)閉,自動(dòng)清理無(wú)效端口
  port.onerror = (err) => {
    console.log('端口錯(cuò)誤:', err);
    connections.delete(port);
  };

  // 顯示激活端口(部分瀏覽器需顯式調(diào)用)
  port.start();
};

Brocastchannel

支持同源下跨標(biāo)簽頁(yè)通信方案,適用于廣播消息方案

  • 創(chuàng)建頻道名稱,只要在同個(gè)頻道名稱下的標(biāo)簽,都能接收廣播消息
  • 無(wú)需像 SharedWorker 那樣遍歷廣播,直接發(fā)送即可
  • 實(shí)現(xiàn)簡(jiǎn)單,幾行代碼即可搞定

存在問(wèn)題:

  • 2022 年才穩(wěn)定的 API ,需要考慮下兼容性問(wèn)題
  • 無(wú)法控制廣播域,需要通過(guò)多頻道來(lái)區(qū)分廣播形式,或者通過(guò)標(biāo)識(shí)符判斷要處理消息的頁(yè)面

使用:

  • 通過(guò) new BroadcastChannel(channelName) 創(chuàng)建頻道
  • 通過(guò)監(jiān)聽(tīng) message 來(lái)監(jiān)聽(tīng)廣播消息
  • 通過(guò) channel.postMessage() 發(fā)送消息
const broadcastChannel = new BroadcastChannel("channel");
// 接收其他同頻道的廣播消息
broadcastChannel.addEventListener("message", (e) => {
  const { type,data } = e.data
});
// 為同頻道廣播消息
broadcastChannle.postMessage({type:'message',data})

父子窗口

當(dāng)使用 window.open() 打開(kāi)窗口,可以通過(guò)父子窗口執(zhí)行通信

  • 利用 瀏覽器窗口句柄(Window Handle)和消息傳遞 支持了父子通信
  • 可以使用父窗口作為中間層,實(shí)現(xiàn)各個(gè)子窗口的流通性以及廣播功能
  • 兼容性好

存在問(wèn)題:

  • 需要窗口之間存在父子引用關(guān)系
  • 需要額外維護(hù)子窗口的引用信息

使用:

  1. 父窗口通過(guò) window.open 打開(kāi)子窗口,同時(shí)保留子窗口的引用
  2. 基于 window.name 區(qū)分子窗口,便于后續(xù)跨子窗口通信
  3. 監(jiān)聽(tīng)各自 windowmessage 事件實(shí)現(xiàn)通信
  4. 父窗口通過(guò) child.postMessage() 發(fā)送消息,子窗口通過(guò) window.opener.postMessage() 回復(fù)消息,如果要跨域,可以通過(guò) postMessage 第二參數(shù)指定目標(biāo)源,* 代表向所有源發(fā)送消息
// 父窗口
const child = window.open('/some-page')
// 給子窗口定義唯一id,便于通信
child.name = 'win_' + id() 
window.addEventlistener('message',()=>{
  // 監(jiān)聽(tīng)子窗口傳遞的消息
})

// 在子窗口 /some-page 頁(yè)面
// 發(fā)送數(shù)據(jù)到父窗口
window.opener.postMessage({type:'message', name:window.name, data})

window.addEventlistener('message',()=>{
  // 監(jiān)聽(tīng)父窗口傳遞的消息
})

Websocket

通過(guò) websocket ,以服務(wù)端做中轉(zhuǎn),實(shí)現(xiàn)跨域通信方案

  • 支持跨域,同時(shí)支持跨瀏覽器跨設(shè)備
  • 可以結(jié)合其他跨標(biāo)簽方案,實(shí)現(xiàn)復(fù)用 websocket 連接的操作

存在問(wèn)題

  • 需要服務(wù)端支持,實(shí)現(xiàn)較為復(fù)雜

MessageChannel

這個(gè)方案還是比較常用在 iframe 跨域通信機(jī)制中,僅記錄

MessageChannel 本質(zhì)也是依賴 MessagePort 來(lái)實(shí)現(xiàn)的通信機(jī)制,本質(zhì)和 SharedWorker 一樣
如果實(shí)現(xiàn)跨標(biāo)簽頁(yè)通訊,需要有中間人且支持結(jié)構(gòu)化克隆算法來(lái)協(xié)助傳遞 port 端口信息

參考內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容