欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript實現(xiàn)瀏覽器不同標(biāo)簽頁通信的原理與實踐

 更新時間:2025年08月29日 09:44:49   作者:Luckily_BAI  
這篇文章主要為大家詳細(xì)系統(tǒng)梳理了不同頁簽(Tab / Window / Frame)之間的通信手段以及具體的實現(xiàn)方法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

系統(tǒng)梳理不同頁簽(Tab / Window / Frame)之間的通信手段:能做什么、怎么做、底層原理、限制與踩坑、降級方案,以及一份可落地的通用消息總線實現(xiàn)。

1. 典型場景與選擇指南

訴求推薦優(yōu)先級說明
同源多標(biāo)簽頁簡單廣播BroadcastChannel → localStorage(storage 事件)現(xiàn)代瀏覽器首選 BroadcastChannel;需要兼容老瀏覽器時降級到 storage 事件
父子窗口/打開者-被打開者直接通信postMessage + Window 引用通過 window.open、window.opener、iframe.contentWindow 獲取引用,再 postMessage
需要單點中樞、復(fù)雜路由或持久化SharedWorker / Service WorkerSharedWorker 做內(nèi)存中樞;Service Worker 能向所有受控客戶端群發(fā),并可持久化
跨設(shè)備或登錄用戶維度的廣播WebSocket / SSE / WebRTC通過后端轉(zhuǎn)發(fā)(或點對點)實現(xiàn)端到端同步
單例任務(wù)/互斥鎖(而非消息)Web Locks API(navigator.locks)不是通信,但常與通信結(jié)合做“選主/互斥”

原則:能用 BroadcastChannel 就別用 localStorage(更干凈、性能好、不阻塞)。要兼容老環(huán)境,再做降級;有復(fù)雜編排再上 Worker。

2. BroadcastChannel:同源廣播的首選

2.1 核心 API

// 建立頻道
const bc = new BroadcastChannel('app_bus');
?
// 接收
bc.onmessage = (event) => {
  // event.data 支持 Structured Clone(可傳對象、ArrayBuffer、MessagePort 等)
  console.log('[BC] recv:', event.data);
};
?
// 發(fā)送
bc.postMessage({ type: 'PING', ts: Date.now() });
?
// 關(guān)閉
bc.close();

2.2 原理與語義

  • 同源標(biāo)簽頁共享同名頻道;瀏覽器內(nèi)部維護訂閱者列表,消息采用 Structured Clone 語義拷貝。
  • 有序性:同一發(fā)送方的消息在同一接收方表現(xiàn)為 FIFO,但不同發(fā)送方之間沒有全局序。
  • 傳輸保障:非持久、非可靠(頁面關(guān)閉或后臺凍結(jié)時可能丟)。

2.3 實戰(zhàn)要點

  • 大消息(> 數(shù)百 KB)不建議直傳,改為 “信令 + IndexedDB 取件” 。
  • 頁面卸載時(visibilitychange/pagehide)清理資源;必要時發(fā)送 LEAVE。
  • 去重:消息附帶 id(如 crypto.randomUUID()),接收方維護 LRU Set 去重。

3. localStorage + storage 事件:兼容兜底

3.1 使用示例

// 監(jiān)聽(只在“其他”標(biāo)簽頁觸發(fā))
window.addEventListener('storage', (e) => {
  if (e.key === 'app_bus') {
    const payload = JSON.parse(e.newValue || 'null');
    if (payload) handleMessage(payload);
  }
});
?
// 發(fā)送(會同步寫磁盤;同頁不觸發(fā) storage 事件)
function send(msg) {
  localStorage.setItem('app_bus', JSON.stringify({
    ...msg,
    id: crypto.randomUUID(),
    ts: Date.now(),
  }));
}

3.2 限制與坑

  • 同步阻塞:setItem 會阻塞主線程,頻繁發(fā)送會卡頓。
  • 同頁不觸發(fā):當(dāng)前頁調(diào)用 setItem 不會觸發(fā)本頁 storage 事件。
  • 只有字符串:需要 JSON 編解碼。
  • 同值不觸發(fā):如果寫入的值未變化,不會觸發(fā)事件(可附帶隨機 nonce)。
  • 隱私模式/分區(qū)存儲:不同容器/場景可能彼此隔離。

適用于“偶發(fā)廣播 + 低頻消息 + 老瀏覽器兼容”。

4. postMessage + Window 引用:點對點直連

4.1 適用場景

  • A 打開 B(window.open),或 A 內(nèi)嵌 B(iframe),或 B 由 A 打開(window.opener)。
  • 同源可直接讀寫;跨源也能 postMessage,但需 targetOrigin 校驗。

4.2 使用示例

// 打開子窗口并握手
const child = window.open('/child.html', 'child');
?
window.addEventListener('message', (event) => {
  // 嚴(yán)格校驗來源
  if (event.origin !== location.origin) return;
  console.log('from child:', event.data);
});
?
// 向子窗口發(fā)送
child?.postMessage({ type: 'PING' }, location.origin);

跨源時:必須使用精確的 targetOrigin(不要用 *),并對 event.origin 做白名單校驗,防止 XSS/點擊劫持類問題。

5. SharedWorker:內(nèi)存中樞

5.1 思路

多個同源頁面連接到同一個 SharedWorker,通過 MessagePort 與之通信;Worker 內(nèi)部充當(dāng)“Hub”做路由/廣播/狀態(tài)管理。

5.2 示例

worker.js

const ports = new Set();
?
onconnect = (e) => {
  const port = e.ports[0];
  ports.add(port);
?
  port.onmessage = (evt) => {
    // 廣播給其他端
    for (const p of ports) {
      if (p !== port) p.postMessage(evt.data);
    }
  };
?
  port.start();
?
  port.addEventListener('close', () => ports.delete(port));
};

頁面:

const sw = new SharedWorker('/worker.js');
const port = sw.port;
port.start();
?
port.onmessage = (e) => console.log('[SW] recv:', e.data);
port.postMessage({ type: 'HELLO' });

5.3 優(yōu)缺點

  • 靈活、可做復(fù)雜編排/緩存;
  • 兼容性較 BroadcastChannel 差一些;調(diào)試門檻略高。

6. Service Worker:全客戶端中轉(zhuǎn)與持久化

6.1 模式

  • 頁面 → SW:navigator.serviceWorker.controller.postMessage
  • SW → 所有頁面:self.clients.matchAll() → client.postMessage

6.2 示例

sw.js

self.addEventListener('message', (event) => {
  // 簡單群發(fā)
  self.clients.matchAll({ includeUncontrolled: true, type: 'window' })
    .then((clients) => {
      clients.forEach((client) => client.postMessage(event.data));
    });
});
?
self.addEventListener('activate', (e) => self.clients.claim());

頁面:

navigator.serviceWorker.register('/sw.js');
?
navigator.serviceWorker.addEventListener('message', (e) => {
  console.log('[SW] recv:', e.data);
});
?
function sendViaSW(data) {
  navigator.serviceWorker.ready.then((reg) => {
    reg.active?.postMessage(data);
  });
}

6.3 要點

  • 僅受控頁面能直接與 SW 通信;首次加載可能未受控,clients.claim() 可加速接管。
  • SW 可結(jié)合 Cache/IndexedDB 做可靠隊列或離線重放。

7. 服務(wù)器中轉(zhuǎn)(WebSocket / SSE / WebRTC)

  • 跨設(shè)備/賬號級廣播的標(biāo)準(zhǔn)方案;同設(shè)備多標(biāo)簽頁也可統(tǒng)一走服務(wù)器,減少本地復(fù)雜度。
  • WebSocket:雙向、低時延;SSE:單向、簡單;WebRTC:P2P,常與信令(WebSocket)結(jié)合。

實戰(zhàn)建議:本地(BC/Worker)優(yōu)先、服務(wù)器兜底。對“必須送達”的關(guān)鍵消息,做ACK + 重試。

8. 更高階:SharedArrayBuffer(SAB)與跨上下文共享

跨源隔離(COOP+COEP)啟用時,可通過 BroadcastChannel / postMessage 傳遞 SharedArrayBuffer,配合 Atomics 做無鎖隊列/環(huán)形緩沖,獲得極低延遲。

復(fù)雜且對環(huán)境要求高,通常用于多媒體/計算密集型場景。

9. 通用“可靠廣播”模式(ACK/去重/重試)

9.1 消息格式

interface BusMessage<T=any> {
  id: string;           // 唯一 ID(UUID v4)
  ts: number;           // 發(fā)送時間戳
  type: string;         // 主題/事件名
  payload: T;           // 載荷
  ack?: boolean;        // 是否為 ACK 消息
  to?: string;          // 指定接收者(可選)
  from?: string;        // 發(fā)送者實例 ID
}

9.2 接收側(cè)去重

維護 seenIds(如 LRU 緩存 1–5 分鐘),收到重復(fù) id 直接丟棄。

9.3 ACK + 重試

發(fā)送后 setTimeout 等待 ACK;超時重發(fā)(指數(shù)退避);達到上限告警。

10. 一個可落地的跨標(biāo)簽頁消息總線(含降級)

目標(biāo):優(yōu)先 BroadcastChannel → 失敗則 Service Worker → 再失敗則 localStorage。

// 簡化版:事件訂閱、可靠發(fā)送(可自行擴展 ACK/重試)
?
type Handler = (msg: any) => void;
?
export class CrossTabBus {
  private channelName: string;
  private bc?: BroadcastChannel;
  private swReady: Promise<ServiceWorkerRegistration> | null = null;
  private lsKey: string;
  private handlers: Map<string, Set<Handler>> = new Map();
  private instanceId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
  private seen = new Set<string>();
?
  constructor(channelName = 'app_bus') {
    this.channelName = channelName;
    this.lsKey = `${channelName}__ls`;
?
    // 1) BroadcastChannel
    try {
      this.bc = new BroadcastChannel(channelName);
      this.bc.onmessage = (e) => this._onMessage(e.data);
    } catch {}
?
    // 2) Service Worker(可選)
    if ('serviceWorker' in navigator) {
      this.swReady = navigator.serviceWorker.ready.catch(() => null as any);
      navigator.serviceWorker.addEventListener('message', (e) => this._onMessage(e.data));
    }
?
    // 3) localStorage 兜底
    window.addEventListener('storage', (e) => {
      if (e.key === this.lsKey && e.newValue) {
        try { this._onMessage(JSON.parse(e.newValue)); } catch {}
      }
    });
  }
?
  on(type: string, fn: Handler) {
    if (!this.handlers.has(type)) this.handlers.set(type, new Set());
    this.handlers.get(type)!.add(fn);
    return () => this.off(type, fn);
  }
?
  off(type: string, fn: Handler) {
    this.handlers.get(type)?.delete(fn);
  }
?
  emit(type: string, payload: any) {
    const msg = { id: crypto.randomUUID?.() || String(Math.random()), ts: Date.now(), type, payload, from: this.instanceId };
    this._fanout(msg);
  }
?
  private _fanout(msg: any) {
    // BroadcastChannel
    if (this.bc) {
      try { this.bc.postMessage(msg); } catch {}
    }
?
    // Service Worker(向 active SW 發(fā)送)
    this.swReady?.then((reg) => reg?.active?.postMessage?.(msg)).catch(() => {});
?
    // localStorage 兜底
    try { localStorage.setItem(this.lsKey, JSON.stringify(msg)); } catch {}
  }
?
  private _onMessage(msg: any) {
    if (!msg || typeof msg !== 'object') return;
    if (this.seen.has(msg.id)) return; // 去重
    this.seen.add(msg.id);
    // LRU 簡化:超過一定大小清理
    if (this.seen.size > 2000) this.seen.clear();
?
    const set = this.handlers.get(msg.type);
    set?.forEach((fn) => fn(msg.payload));
  }
}

使用:

import { CrossTabBus } from './CrossTabBus';
?
const bus = new CrossTabBus('my_app_bus');
?
bus.on('user:logout', () => {
  // 做登出清理
  location.reload();
});
?
// 比如收到服務(wù)端事件,廣播給所有標(biāo)簽頁
function onServerLogout() {
  bus.emit('user:logout', { reason: 'token_expired' });
}

11. 選主(Leader Election)與單例任務(wù)

目的:同一站點只讓一個標(biāo)簽頁跑“定時同步/心跳/后臺任務(wù)”。

11.1 BroadcastChannel + 心跳

const bc = new BroadcastChannel('leader');
const myId = crypto.randomUUID();
let isLeader = false;
let lastBeat = Date.now();
?
function tryElect() {
  // 若長時間未收到 leader 心跳,則自薦
  if (Date.now() - lastBeat > 3000 && !isLeader) {
    bc.postMessage({ type: 'ELECT', id: myId, t: Date.now() });
  }
}
?
bc.onmessage = (e) => {
  const m = e.data;
  if (m.type === 'BEAT') lastBeat = Date.now();
  if (m.type === 'ELECT') {
    // 簡單“Bully”:ID 更大者勝出
    if (m.id > myId) isLeader = false; else isLeader = true;
  }
};
?
setInterval(() => {
  tryElect();
  if (isLeader) bc.postMessage({ type: 'BEAT', id: myId });
}, 1000);

更嚴(yán)謹(jǐn)可用 Web Locks API

navigator.locks.request('singleton-task', { mode: 'exclusive' }, async () => {
  // 只有獲得鎖的標(biāo)簽頁會執(zhí)行這里
  await runBackgroundJob();
});

12. 安全、性能與可靠性

12.1 安全

  • postMessage 必須指定精確 targetOrigin,并校驗 event.origin。
  • 對所有外部輸入(消息)做結(jié)構(gòu)校驗(如 zod/superstruct)。
  • 不要把敏感數(shù)據(jù)放入 localStorage 明文廣播。

12.2 性能

  • localStorage.setItem 會阻塞主線程;高頻通信避免使用。
  • 大對象建議經(jīng) IndexedDB 持久化,消息只帶“索引”。
  • 背景標(biāo)簽頁計時器可能被節(jié)流,心跳/重試策略要容忍抖動。

12.3 可靠性

  • 關(guān)鍵消息實現(xiàn) ACK/重試/去重
  • 頁面卸載前(pagehide/visibilitychange)做最后通知;
  • 對 SW/SharedWorker 引入 健康檢查重新連接

13. 兼容性與降級建議

首選 BroadcastChannel;如果環(huán)境不支持:

  • 若存在 SW:走 SW 中轉(zhuǎn);
  • 否則:storage 事件兜底。

SharedWorker 在部分瀏覽器/版本支持較弱,盡量作為可選增強。

IE 等古老環(huán)境:只能用 storage 事件 / postMessage(在能拿到引用的前提下)。

14. 常見需求的實現(xiàn)清單

  • 跨標(biāo)簽頁單點登錄/登出同步:Bus 廣播 user:logout,收到后清 Token + 刷新。
  • 表單協(xié)同編輯(同賬號) :頻道內(nèi)發(fā)送 cursor/patch,并對本地變更做去抖與去重。
  • 通知徽標(biāo)同步:收到服務(wù)器通知后在一個標(biāo)簽頁拉取計數(shù),再廣播至其他標(biāo)簽頁。
  • “只保留一個播放實例” :選主后非 Leader 收到 play 指令時轉(zhuǎn)成 pause。

15. 小結(jié)

  • BroadcastChannel 是同源多頁簽通信的“現(xiàn)代默認(rèn)”;
  • localStorage(storage 事件) 是簡易兼容兜底;
  • postMessage 適用于存在窗口引用的點對點場景;
  • SharedWorker / Service Worker 能承載更復(fù)雜的中樞化邏輯;
  • 做到可觀測、可恢復(fù)、可降級,你的跨頁通信就能穩(wěn)如老狗。

以上就是JavaScript實現(xiàn)瀏覽器不同標(biāo)簽頁通信的原理與實踐的詳細(xì)內(nèi)容,更多關(guān)于JavaScript瀏覽器不同標(biāo)簽頁通信的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • php中給js數(shù)組賦值方法

    php中給js數(shù)組賦值方法

    PHP函數(shù)庫提供了編/解碼JSON的函數(shù):json_encode()和json_decode(),可以比較方便的傳遞數(shù)組或?qū)ο蠼ojavascript
    2014-03-03
  • js學(xué)使用setTimeout實現(xiàn)輪循動畫

    js學(xué)使用setTimeout實現(xiàn)輪循動畫

    這篇文章主要為大家詳細(xì)介紹了js使用setTimeout實現(xiàn)輪循動畫,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • 詳解JavaScript中的原型和原型鏈

    詳解JavaScript中的原型和原型鏈

    這篇文章主要為大家介紹了JavaScript中的原型和原型鏈,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-11-11
  • JS使用cookie實現(xiàn)DIV提示框只顯示一次的方法

    JS使用cookie實現(xiàn)DIV提示框只顯示一次的方法

    這篇文章主要介紹了JS使用cookie實現(xiàn)DIV提示框只顯示一次的方法,涉及JavaScript基于cookie標(biāo)記控制頁面元素樣式修改的技巧,需要的朋友可以參考下
    2015-11-11
  • js實現(xiàn)轉(zhuǎn)動骰子模型

    js實現(xiàn)轉(zhuǎn)動骰子模型

    這篇文章主要為大家詳細(xì)介紹了js實現(xiàn)轉(zhuǎn)動骰子模型,自動隨機生成骰子數(shù)的模型,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • JS+Canvas繪制動態(tài)時鐘效果

    JS+Canvas繪制動態(tài)時鐘效果

    這篇文章主要為大家詳細(xì)介紹了JS+Canvas繪制動態(tài)時鐘效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • javascript 正則表達式(一)

    javascript 正則表達式(一)

    javascript 正則表達式使用實例,挺有意思的,既不是基礎(chǔ)也不是技巧就是解決例子。
    2010-05-05
  • 前端實現(xiàn)個人信息脫敏(手機號、身份證號、姓名、郵箱)JavaScript代碼示例

    前端實現(xiàn)個人信息脫敏(手機號、身份證號、姓名、郵箱)JavaScript代碼示例

    這篇文章主要介紹了如何使用JavaScript對手機號、身份證號、姓名和郵箱等個人數(shù)據(jù)進行脫敏處理,以保護用戶隱私,通過正則表達式和分組捕獲,可以靈活地對不同類型的數(shù)據(jù)進行脫敏處理,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-02-02
  • JS sort方法基于數(shù)組對象屬性值排序

    JS sort方法基于數(shù)組對象屬性值排序

    這篇文章主要介紹了JS sort方法基于數(shù)組對象屬性值排序,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07
  • jquery操作下拉列表、文本框、復(fù)選框、單選框集合(收藏)

    jquery操作下拉列表、文本框、復(fù)選框、單選框集合(收藏)

    jquery操作拉列表、文本框、復(fù)選框、單選框集合。各種對下拉列表、文本框、復(fù)選框、單選框的jquery的相關(guān)操作。做為記錄和收藏的最好方法
    2014-01-01

最新評論