第一章 前言

在前端後台管理開發的過程中,我們常常會遇到這樣的需求:用户在瀏覽器中打開了同一個網站的多個標籤頁,我們需要在這些標籤頁之間進行通信。比如,當用户在一個標籤頁中修改了某些數據,我們希望其他標籤頁能夠及時更新。這種跨標籤頁通信的需求在實際開發中非常常見,但是同時也是經常忽略的一個點,通常説刷一下之後就好了,不用那麼麻煩。但是小編為了解決這一問題,來與大家分享一下如何實現前端跨標籤頁通信。

第二章 需要跨標籤通信的原因

在實際的業務場景中,跨標籤頁通信的需求主要體現在以下幾個方面:

  1. 數據同步:當用户在多個標籤頁中操作同一個數據時,需要保持數據的一致性。例如,在一個電商網站中,用户可能在多個標籤頁中查看不同的商品,當用户在一個標籤頁中將商品加入購物車時,其他標籤頁的購物車數量也需要及時更新。
  2. 狀態共享:有些應用需要在多個標籤頁之間共享狀態。比如,一個在線文檔編輯器,用户可能在多個標籤頁中打開同一個文檔,需要保持文檔的編輯狀態一致。
  3. 性能優化:通過跨標籤頁通信,我們可以避免在每個標籤頁中重複加載相同的數據,從而提高應用的性能。

第三章 實現跨標籤通信的方法以及優缺點

3.1 方法一:BroadcastChannel

  • 原理:BroadcastChannel 是瀏覽器原生支持的跨標籤頁通訊 API,允許在同一源(origin)下的不同標籤頁之間進行消息傳遞。
  • 發送與接收端代碼如下:

        發送端:(小編這裏做的是跨頁面列表初始化的功能)

// 創建一個 BroadcastChannel 實例
const channel = new BroadcastChannel('my-channel')
// 發送消息
channel.postMessage({
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
})

        接收端

// 創建一個 BroadcastChannel 實例
const channel = new BroadcastChannel('my-channel')

// 監聽消息
channel.onmessage = (event) => {
  console.log('Received message:', event.data)
  if (event.data.type === 'init') {
    const { page, limit } = event.data.data
    currentPage.value = page
    pageSize.value = limit
    // 初始化列表
    getList()
  }
}
  • 優點
  1. API 設計簡單,易於理解和使用
  2. 消息是實時傳遞的
  3. 即使數據沒有改變也會檢測到
  • 缺點
  1. 只能在相同源(協議 + 域名 + 端口)的標籤頁之間通信

3.2 方法二:localStorage

  • 原理:使用localStorage能實現是因為我們開出的新標籤頁在都是同一個協議、域名、端口號下,它們的本地存儲內容是共享的!(也就是同源下的不同標籤頁之間可以共享數據)
  • 使用localStorage(看技術棧,如果是vue,也可以用vuex/pinia等,原理是類似的),發送與接收端代碼如下:

        發送端

// 先清除原來數據
localStorage.removeItem('PAGE_INIT')
// 重新賦值(這樣做接受端一定能檢測到變化)
localStorage.setItem('PAGE_INIT', JSON.stringify({ page: 1, limit: 10 }))

// 注:其實不用跟小編一樣,這麼處理只要能讓本地存儲的數據變化就能實現需求

        接收端

// 監聽存儲信息
window.addEventListener('storage', (e) => {
  if (e.key === 'PAGE_INIT') {
    console.log('e.newValue', e)
    const { page, limit } = JSON.parse(e.newValue)
    console.log('數據',page, limt )
    currentPage.value = page
    pageSize.value = limit
    getList()
  }
})
  • 優點
  1. localStorage瀏覽器原生自帶的,兼容性高,幾乎所有瀏覽器都兼容
  2. 使用簡單,發送消息的窗口不會觸發監聽事件
  3. 數據持久化
  • 缺點
  1. 新標籤必須遵守同源協議接受端才能接收到(同源策略)
  2. 如果監聽的值未發生改變也不會觸發事件
  3. 當前標籤頁不會觸發

3.3 方法三:SharedWorker

  • 原理:ShareWorker是一種特殊的 Web Worker,允許多個標籤頁共享同一個 Worker 實例,從而實現跨標籤頁通信
  • 新建worker文件下添加shared-worker.js文件

Web前端面試指導(三十四):如何實現瀏覽器內多個標籤頁之間的通信?_#前端

// shared-worker.js

// 每個頁面都會創建一個port
const ports = [];

onconnect = (event) => {
    console.log('shared-worker onconnect', event);
    // 獲取當前頁的port
    const port = event.ports[0];

    // 將當前頁面的port加入到數組
    ports.push(port);

    port.onmessage = (event) => {
        // 廣播消息到所有連接的標籤頁
        ports.forEach((p) => {
            if (p !== port) {
                p.postMessage(event.data);
            }
        });
    };
};
  • 發送與接收端代碼如下:

        發送端:

import workerjs from '@/worker/shared-worker.js?url'

// 創建實例
const worker = new SharedWorker(workerjs)

// 發送消息到shared-worker
worker.port.postMessage({
  type: 'init',
  data: {
    page: 1,
    size: 10
  }
})

        接收端:

import workerjs from '@/worker/shared-worker.js?url'

// 創建實例
const worker = new SharedWorker(workerjs)

// 接收消息
worker.port.onmessage = (event) => {
  console.log('shared-worker onmessage', event.data)
  if (event.data.type === 'init') {
    // 初始化
    const {
      data: { page, size }
    } = event.data
    currentPage.value = page
    pageSize.value = size
    getList()
  }
}
  • 優點
  1. 多個標籤頁共享同一個 Worker 實例
  2. 適合需要複雜邏輯處理的場景
  • 缺點
  1. 部分瀏覽器或者低版本瀏覽器可能不支持
  • 官方文檔

SharedWorker - Web API | MDN

3.4 方法四:Window.postMessage

  • 原理:通過 Window.postMessage 可以在不同窗口或標籤頁之間傳遞消息。

(注意:小編在跨標籤頁通信時,不推薦該方法,可看下面的缺點)

  • 優點
  1. 可以在不同源的窗口或標籤頁之間通信
  2. 可以指定目標窗口的origin,從而確保消息來源可信(安全性)
  • 缺點
  1. 需要持有目標窗口的引用,即需要開新的標籤頁,原有標籤頁沒法監聽到(理解:目前已有標籤頁1、標籤頁2,需要在標籤頁2做某些處理,標籤頁1能監聽到消息沒法實現,只能在標籤頁2開新的窗口標籤頁3)

3.5 方法五:Service Worker

  • 原理:Service Worker 可以攔截網絡請求並緩存資源,同時也可以用於跨標籤頁通信。
  • 發送端與接收端代碼如下:
// 註冊 Service Worker
navigator.serviceWorker.register('service-worker.js');

// 發送消息
navigator.serviceWorker.controller.postMessage({
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
});

// 接收消息
navigator.serviceWorker.onmessage = (event) => {
  console.log('Received message:', event.data);
}
  • 優點:
  1. Service Worker 可以用於離線緩存
  2. 適合需要複雜邏輯處理的場景
  • 缺點:
  1. 部分瀏覽器或者低版本瀏覽器可能不支持

3.6 方法六:IndexedDB

  • IndexedDB 是一種瀏覽器端數據庫,可以存儲大量結構化數據。通過監聽 IndexedDB 的變化,可以實現跨標籤頁通信
  • 該方案小編也沒有具體實操過,代碼如下:

        發送端將信息寫入IndexedDB:

const data = {
  type: 'init',
  data: {
    page: 1,
    limit: 10
  }
};
const dbName = "simpleDB";
const storeName = "SharedData";
const key = "sharedKey";

const request = indexedDB.open(dbName, 1);

request.onupgradeneeded = e => {
  const db = e.target.result;
  if (!db.objectStoreNames.contains(storeName)) {
    db.createObjectStore(storeName);
  }
};

request.onsuccess = e => {
  const db = e.target.result;
  console.log("db", db);
  const tx = db.transaction(storeName, "readwrite");
  const store = tx.objectStore(storeName);

  // 保存數據
  store.put(data, key);

  tx.oncomplete = () => {
     // 關閉連接
    db.close();
  };
};

        接收端監聽IndexedDB信息變化

getDBData() {
    const dbName = "simpleDB";
    const storeName = "SharedData";
    const key = "sharedKey";

    const request = indexedDB.open(dbName, 1);

    request.onupgradeneeded = e => {
      const db = e.target.result;
      if (!db.objectStoreNames.contains(storeName)) {
        db.createObjectStore(storeName);
      }
    };

    request.onsuccess = e => {
      const db = e.target.result;
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const getRequest = store.get(key);

      getRequest.onsuccess = () => {
        const data = getRequest.result;
        db.close();
      };
    }
}
// 監聽數據變化--這裏使用setInterval
setInterval(() => {
    getDB();
}, 2000);
// 版本號變化監聽-onversionchange
db.onversionchange = () => {
    console.log('數據庫已更新,重新加載數據...');
}
  • 優點:
  1. 適合存儲大量數據
  2. 所有操作都是異步的
  3. 幾乎所有現代瀏覽器都支持
  • 缺點:
  1. 需要大傢俱體實操感受
  • 注意問題
  1. 無痕模式下無法使用indexedDB
  2. 取決於瀏覽器用户的主動行為,當用户拒絕訪問時,不被允許

3.7 方法七:WebSocket

  • 這裏不做過多介紹,該方法需要前後端配合,有點"大材小用"

第四章 總結

方法

特點

適用場景

兼容性

BroadcastChannel

簡單易用,實時性強,同源限制

同源標籤頁之間的實時通信(基本上簡單需求都能用)

低版本ie除外

localStorage

兼容性好,數據持久化,事件觸發

簡單的狀態同步

基本上支持

SharedWorker

共享實例,適合複雜場景

需要複雜邏輯處理的場景

IE 和部分移動瀏覽器除外

Window.postMessage

跨域支持,安全性高,需要窗口跳轉

跨域窗口或標籤頁之間的通信

基本上支持

Service Worker

離線支持,適合複雜場景

需要離線支持的場景

ie除外

IndexedDB

大數據支持,異步操作

需要存儲大量數據的場景

基本上支持

WebSocket

前後端,實時

實時通信

基本上支持