第一章 前言
在前端後台管理開發的過程中,我們常常會遇到這樣的需求:用户在瀏覽器中打開了同一個網站的多個標籤頁,我們需要在這些標籤頁之間進行通信。比如,當用户在一個標籤頁中修改了某些數據,我們希望其他標籤頁能夠及時更新。這種跨標籤頁通信的需求在實際開發中非常常見,但是同時也是經常忽略的一個點,通常説刷一下之後就好了,不用那麼麻煩。但是小編為了解決這一問題,來與大家分享一下如何實現前端跨標籤頁通信。
第二章 需要跨標籤通信的原因
在實際的業務場景中,跨標籤頁通信的需求主要體現在以下幾個方面:
- 數據同步:當用户在多個標籤頁中操作同一個數據時,需要保持數據的一致性。例如,在一個電商網站中,用户可能在多個標籤頁中查看不同的商品,當用户在一個標籤頁中將商品加入購物車時,其他標籤頁的購物車數量也需要及時更新。
- 狀態共享:有些應用需要在多個標籤頁之間共享狀態。比如,一個在線文檔編輯器,用户可能在多個標籤頁中打開同一個文檔,需要保持文檔的編輯狀態一致。
- 性能優化:通過跨標籤頁通信,我們可以避免在每個標籤頁中重複加載相同的數據,從而提高應用的性能。
第三章 實現跨標籤通信的方法以及優缺點
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()
}
}
- 優點:
- API 設計簡單,易於理解和使用
- 消息是實時傳遞的
- 即使數據沒有改變也會檢測到
- 缺點:
- 只能在相同源(協議 + 域名 + 端口)的標籤頁之間通信
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()
}
})
- 優點:
- localStorage瀏覽器原生自帶的,兼容性高,幾乎所有瀏覽器都兼容
- 使用簡單,發送消息的窗口不會觸發監聽事件
- 數據持久化
- 缺點:
- 新標籤必須遵守同源協議接受端才能接收到(同源策略)
- 如果監聽的值未發生改變也不會觸發事件
- 當前標籤頁不會觸發
3.3 方法三:SharedWorker
- 原理:ShareWorker是一種特殊的 Web Worker,允許多個標籤頁共享同一個 Worker 實例,從而實現跨標籤頁通信。
- 新建worker文件下添加shared-worker.js文件
// 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()
}
}
- 優點:
- 多個標籤頁共享同一個 Worker 實例
- 適合需要複雜邏輯處理的場景
- 缺點:
- 部分瀏覽器或者低版本瀏覽器可能不支持
- 官方文檔
SharedWorker - Web API | MDN
3.4 方法四:Window.postMessage
- 原理:通過 Window.postMessage 可以在不同窗口或標籤頁之間傳遞消息。
(注意:小編在跨標籤頁通信時,不推薦該方法,可看下面的缺點)
- 優點:
- 可以在不同源的窗口或標籤頁之間通信
- 可以指定目標窗口的origin,從而確保消息來源可信(安全性)
- 缺點:
- 需要持有目標窗口的引用,即需要開新的標籤頁,原有標籤頁沒法監聽到(理解:目前已有標籤頁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);
}
- 優點:
- Service Worker 可以用於離線緩存
- 適合需要複雜邏輯處理的場景
- 缺點:
- 部分瀏覽器或者低版本瀏覽器可能不支持
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('數據庫已更新,重新加載數據...');
}
- 優點:
- 適合存儲大量數據
- 所有操作都是異步的
- 幾乎所有現代瀏覽器都支持
- 缺點:
- 需要大傢俱體實操感受
- 注意問題:
- 無痕模式下無法使用indexedDB
- 取決於瀏覽器用户的主動行為,當用户拒絕訪問時,不被允許
3.7 方法七:WebSocket
- 這裏不做過多介紹,該方法需要前後端配合,有點"大材小用"
第四章 總結
|
方法
|
特點
|
適用場景
|
兼容性
|
|
BroadcastChannel
|
簡單易用,實時性強,同源限制
|
同源標籤頁之間的實時通信(基本上簡單需求都能用)
|
低版本ie除外
|
|
localStorage
|
兼容性好,數據持久化,事件觸發
|
簡單的狀態同步
|
基本上支持
|
|
SharedWorker
|
共享實例,適合複雜場景
|
需要複雜邏輯處理的場景
|
IE 和部分移動瀏覽器除外
|
|
Window.postMessage
|
跨域支持,安全性高,需要窗口跳轉
|
跨域窗口或標籤頁之間的通信
|
基本上支持
|
|
Service Worker
|
離線支持,適合複雜場景
|
需要離線支持的場景
|
ie除外
|
|
IndexedDB
|
大數據支持,異步操作
|
需要存儲大量數據的場景
|
基本上支持
|
|
WebSocket
|
前後端,實時
|
實時通信
|
基本上支持
|