大型門(mén)戶網(wǎng)站建設(shè)方案網(wǎng)絡(luò)運(yùn)營(yíng)策劃
跨標(biāo)簽頁(yè)通信的常見(jiàn)方案
LocalStorage 或 SessionStorage
BroadCast Channel
Service Worker
Shared Worker
Window.postMessage()
Cookies
IndexedDB
什么是跨標(biāo)簽頁(yè)通信?
指在同一個(gè)瀏覽器窗口中的多個(gè)標(biāo)簽頁(yè)之間進(jìn)行數(shù)據(jù)交流和信息傳遞的過(guò)程。通常情況下,每個(gè)標(biāo)簽頁(yè)都是一個(gè)獨(dú)立的瀏覽器上下文,它們之間是相互隔離的,無(wú)法直接訪問(wèn)對(duì)方的數(shù)據(jù)或進(jìn)行通信。
跨標(biāo)簽頁(yè)通信的目的是允許這些相互隔離的標(biāo)簽頁(yè)之間進(jìn)行信息共享和交互。通過(guò)跨標(biāo)簽頁(yè)通信,可以實(shí)現(xiàn)數(shù)據(jù)的共享、狀態(tài)的同步、消息的傳遞等功能。
例如:在一個(gè)標(biāo)簽頁(yè)中進(jìn)行了某個(gè)操作,希望其他標(biāo)簽頁(yè)能夠及時(shí)獲得相關(guān)的變化和通知,就需要使用跨標(biāo)簽頁(yè)通信機(jī)制來(lái)實(shí)現(xiàn)這種交互。
跨標(biāo)簽頁(yè)通信主要用于哪些需求?
① 數(shù)據(jù)共享:當(dāng)多個(gè)標(biāo)簽頁(yè)需要訪問(wèn)和共享相同的數(shù)據(jù)時(shí),跨標(biāo)簽頁(yè)通信可以用于在這些標(biāo)簽頁(yè)之間傳遞數(shù)據(jù),確保它們保持同步。
② 狀態(tài)同步:在一些應(yīng)用中,可能會(huì)有多個(gè)標(biāo)簽頁(yè)用于展示相同的應(yīng)用狀態(tài)或會(huì)話狀態(tài)。通過(guò)跨標(biāo)簽頁(yè)通信,可以實(shí)現(xiàn)狀態(tài)的同步,使得在一個(gè)標(biāo)簽頁(yè)中的操作能夠即時(shí)反映到其他標(biāo)簽頁(yè)上。
③ 消息通知:跨標(biāo)簽頁(yè)通信可以用于實(shí)現(xiàn)在一個(gè)標(biāo)簽頁(yè)中發(fā)送消息,然后其他標(biāo)簽頁(yè)接收并展示這些消息的功能。
④ 共享資源:在某些場(chǎng)景下,可能需要在多個(gè)標(biāo)簽頁(yè)之間共享某些資源,如網(wǎng)絡(luò)連接、音頻/視頻播放器等等。
⑤ 多窗口管理:對(duì)于一些具有多個(gè)窗口的應(yīng)用,跨標(biāo)簽頁(yè)通信可以用于實(shí)現(xiàn)窗口之間的聯(lián)動(dòng)和數(shù)據(jù)同步。
常見(jiàn)方案的實(shí)現(xiàn)
方案一:LocalStorage 或 SessionStorage
使用 Web 存儲(chǔ)機(jī)制( LocalStorage 或 SessionStorage )可以在不同標(biāo)簽頁(yè)之間共享數(shù)據(jù)。一個(gè)標(biāo)簽頁(yè)可以將數(shù)據(jù)存儲(chǔ)在 LocalStorage 或 SessionStorage 中,其他標(biāo)簽頁(yè)可以監(jiān)聽(tīng)存儲(chǔ)事件來(lái)獲取更新的數(shù)據(jù)。
/* 在一個(gè)標(biāo)簽頁(yè)中寫(xiě)入數(shù)據(jù)到 LocalStorage 或 SessionStorage */
localStorage.setItem('shareData', '標(biāo)簽頁(yè)111');
// sessionStorage.setItem('shareData', '標(biāo)簽頁(yè)111');/* 在其他標(biāo)簽頁(yè)中監(jiān)聽(tīng)存儲(chǔ)事件,并獲取更新的數(shù)據(jù) */
window.addEventListener('storage', function(event) {if (event.key === 'shareData') {const newData = event.newValue;console.log('收到的更新數(shù)據(jù):', newData);}
});/* 在另一個(gè)標(biāo)簽頁(yè)中更新數(shù)據(jù)到 LocalStorage 或 SessionStorage */
localStorage.setItem('shareData', '標(biāo)簽頁(yè)222');
// sessionStorage.setItem('shareData', '標(biāo)簽頁(yè)222');
首先在一個(gè)標(biāo)簽頁(yè)中通過(guò) localStorage.setItem() 或 sessionStorage.setItem() 方法將數(shù)據(jù)寫(xiě)入到 LocalStorage 或 ?SessionStorage 中。
然后在其他標(biāo)簽頁(yè)中通過(guò)監(jiān)聽(tīng) storage 事件來(lái)捕獲存儲(chǔ)事件,并判斷事件的 key 是否為我們共享的數(shù)據(jù) sharedData,如果是,則獲取更新的數(shù)據(jù) newValue 并進(jìn)行處理。
最后在另一個(gè)標(biāo)簽頁(yè)中通過(guò) localStorage.setItem() 或 sessionStorage.setItem() 方法更新數(shù)據(jù)。
方案二:Broadcast Channel
Broadcast Channel API 允許不同標(biāo)簽頁(yè)之間通過(guò)共享的通道進(jìn)行消息廣播和接收。一個(gè)標(biāo)簽頁(yè)可以通過(guò)通道發(fā)送消息,其他訂閱了相同通道的標(biāo)簽頁(yè)可以接收到這些消息。
在發(fā)送消息的標(biāo)簽頁(yè)中
/* 創(chuàng)建一個(gè)廣播通道 */
const channelObj = new BroadcastChannel('televiseChannel');// 發(fā)送消息
channelObj.postMessage('標(biāo)簽頁(yè)111');
在接收消息的標(biāo)簽頁(yè)中
/* 創(chuàng)建一個(gè)廣播通道 */
const channelObj = new BroadcastChannel('televiseChannel');// 監(jiān)聽(tīng)消息事件
channelObj.onmessage = function(event) {const newData = event.data;console.log('收到的更新數(shù)據(jù):', newData);
};
在發(fā)送消息的標(biāo)簽頁(yè)中創(chuàng)建一個(gè) Broadcast Channel,并指定一個(gè)唯一的通道名稱(這里使用'televiseChannel')。通過(guò) channelObj.postMessage() 方法發(fā)送消息到該通道。
在接收消息的標(biāo)簽頁(yè)中,同樣創(chuàng)建一個(gè)具有相同通道名稱的 Broadcast Channel。然后通過(guò)為 channelObj.onmessage 賦值一個(gè)函數(shù)來(lái)監(jiān)聽(tīng)消息事件。當(dāng)接收到消息時(shí),事件對(duì)象 event 中的 data 屬性將包含發(fā)送的消息內(nèi)容,我們可以在監(jiān)聽(tīng)函數(shù)中獲取并處理該消息。
方案三:Service Worker
Service Worker 是一種獨(dú)立于網(wǎng)頁(yè)的腳本,可以在后臺(tái)運(yùn)行,提供離線緩存和消息傳遞等功能。標(biāo)簽頁(yè)可以通過(guò) Service Worker 進(jìn)行通信,發(fā)送消息和接收消息。
方案四:Shared Worker
Shared Worker 是一種在多個(gè)標(biāo)簽頁(yè)之間共享的后臺(tái)線程。標(biāo)簽頁(yè)可以通過(guò) SharedWorker 進(jìn)行通信,發(fā)送消息和接收消息。這種方式需要使用 JavaScript 的 Worker API。
在發(fā)送消息的標(biāo)簽頁(yè)中
/* 創(chuàng)建一個(gè) SharedWorker */
const worker = new SharedWorker('worker.js');// 發(fā)送消息
worker.port.postMessage('標(biāo)簽頁(yè)111');
在共享的 Worker 腳本文件 worker.js 中
/* 監(jiān)聽(tīng)連接事件 */
self.onconnect = function(event) {const port = event.ports[0];// 監(jiān)聽(tīng)消息事件port.onmessage = function(event) {const newData = event.data;console.log('收到的更新數(shù)據(jù):', newData);};// 發(fā)送消息port.postMessage('你好啊!Worker');
};
在發(fā)送消息的標(biāo)簽頁(yè)中創(chuàng)建一個(gè) SharedWorker,并指定共享的 Worker 腳本文件路徑為 'worker.js'。然后通過(guò) worker.port.postMessage() 方法發(fā)送消息到 SharedWorker。
在共享的 Worker 腳本文件 worker.js 中,通過(guò)監(jiān)聽(tīng) self.onconnect 事件來(lái)捕獲連接事件,并獲取與標(biāo)簽頁(yè)之間的通信端口 port。然后通過(guò)為 port.onmessage 賦值一個(gè)函數(shù)來(lái)監(jiān)聽(tīng)消息事件。當(dāng)接收到消息時(shí),事件對(duì)象 event 中的 data 屬性將包含發(fā)送的消息內(nèi)容,我們可以在監(jiān)聽(tīng)函數(shù)中獲取并處理該消息。
方案五:Window.postMessage()
Window.postMessage() 方法允許在不同的窗口或標(biāo)簽頁(yè)之間安全地傳遞消息。通過(guò)調(diào)用?postMessage() 方法并指定目標(biāo)窗口的 origin,可以將消息發(fā)送到其他標(biāo)簽頁(yè),并通過(guò)監(jiān)聽(tīng)?message 事件來(lái)接收消息。
在發(fā)送消息的標(biāo)簽頁(yè)中
/* 監(jiān)聽(tīng)消息事件 */
window.addEventListener('message', function(event) {// 確保消息來(lái)自預(yù)期的源if (event.origin !== 'http://example.com') return;const newData = event.data;console.log('收到的更新數(shù)據(jù):', newData);
});// 發(fā)送消息到其他標(biāo)簽頁(yè)
const targetWindow = window.open('http://example.com/otherpage', '_blank');
targetWindow.postMessage('標(biāo)簽頁(yè)111', 'http://example.com');
在接收消息的標(biāo)簽頁(yè)中
/* 監(jiān)聽(tīng)消息事件 */
window.addEventListener('message', function(event) {// 確保消息來(lái)自預(yù)期的源if (event.origin !== 'http://example.com') return;const newData = event.data;console.log('收到的更新數(shù)據(jù):', newData);// 回復(fù)消息event.source.postMessage('標(biāo)簽頁(yè)222', event.origin);
});
在發(fā)送消息的標(biāo)簽頁(yè)中通過(guò)使用 window.addEventListener('message', ...) 監(jiān)聽(tīng)消息事件。在事件處理函數(shù)中,可以用 event.origin 來(lái)驗(yàn)證消息的來(lái)源是否符合預(yù)期。然后,可以用 event.data 獲取到發(fā)送的消息內(nèi)容,并進(jìn)行相應(yīng)的操作。
在發(fā)送消息的標(biāo)簽頁(yè)中,用 window.open() 打開(kāi)了一個(gè)新的標(biāo)簽頁(yè)(http://example.com/otherpage),然后通用 targetWindow.postMessage() 向該標(biāo)簽頁(yè)發(fā)送消息。在這里,我們指定了消息的目標(biāo)窗口和預(yù)期的來(lái)源(即目標(biāo)標(biāo)簽頁(yè)的 URL)。
在接收消息的標(biāo)簽頁(yè)中,同樣通過(guò) window.addEventListener('message', ...) 監(jiān)聽(tīng)消息事件,并在事件處理函數(shù)中進(jìn)行相應(yīng)的操作。
方案六:Cookies
可以將需要共享的數(shù)據(jù)存儲(chǔ)在 Cookies 中,并在不同的標(biāo)簽頁(yè)之間讀取和更新這些 Cookies。當(dāng)一個(gè)標(biāo)簽頁(yè)更新數(shù)據(jù)時(shí),將數(shù)據(jù)寫(xiě)入到 Cookies 中,其他標(biāo)簽頁(yè)可以通過(guò)監(jiān)聽(tīng) Cookies 變化事件或定時(shí)讀取 Cookies 來(lái)獲取最新的數(shù)據(jù)。
使用 Cookies 進(jìn)行通信是一種簡(jiǎn)單的方法,但它主要用于在客戶端和服務(wù)器之間傳遞數(shù)據(jù),而不是直接實(shí)現(xiàn)跨標(biāo)簽頁(yè)通信。Cookies 會(huì)自動(dòng)在客戶端和服務(wù)器之間進(jìn)行傳遞,因此可以在不同的標(biāo)簽頁(yè)之間共享數(shù)據(jù)。
在發(fā)送消息的標(biāo)簽頁(yè)中
/* 設(shè)置 Cookie 值 */
document.cookie = 'shareData=標(biāo)簽頁(yè)111';
在接收消息的標(biāo)簽頁(yè)中
/* 獲取 Cookie 值 */
const cookies = document.cookie;
const cookieArr = cookies.split(';');
const strField = 'shareData=';let newData = null;
for (let i = 0; i < cookieArr.length; i++) {const cookie = cookieArr[i].trim();if (cookie.startsWith(strField)) {newData = cookie.substring(strField.length, cookie.length);break;}
}
console.log('收到的更新數(shù)據(jù):', newData);
方案七:IndexedDB
IndexedDB 是瀏覽器提供的一個(gè)客戶端數(shù)據(jù)庫(kù),可以在不同的標(biāo)簽頁(yè)之間存儲(chǔ)和讀取數(shù)據(jù)。一個(gè)標(biāo)簽頁(yè)可以將數(shù)據(jù)寫(xiě)入 IndexedDB,其他標(biāo)簽頁(yè)可以監(jiān)聽(tīng) IndexedDB 的變化事件或定時(shí)從 IndexedDB 中讀取數(shù)據(jù)來(lái)實(shí)現(xiàn)數(shù)據(jù)的共享和狀態(tài)的同步。
/* 打開(kāi)或創(chuàng)建 IndexedDB 數(shù)據(jù)庫(kù) */
const request = indexedDB.open('dataBase', 1);/* 成功打開(kāi)數(shù)據(jù)庫(kù) */
request.onsuccess = function(event) {const db = event.target.result;// 創(chuàng)建一個(gè)對(duì)象存儲(chǔ)空間(類似表)const objectStore = db.createObjectStore('messages', { keyPath: 'id', autoIncrement: true });// 添加一條消息到對(duì)象存儲(chǔ)空間const message = { text: 'Hello, World!' };const addRequest = objectStore.add(message);addRequest.onsuccess = function(event) {console.log('消息已添加到IndexedDB');};addRequest.onerror = function(event) {console.error('添加消息到IndexedDB時(shí)發(fā)生錯(cuò)誤');};// 從對(duì)象存儲(chǔ)空間獲取所有消息const getAllRequest = objectStore.getAll();getAllRequest.onsuccess = function(event) {const messages = event.target.result;console.log('所有消息:', messages);};getAllRequest.onerror = function(event) {console.error('獲取消息時(shí)發(fā)生錯(cuò)誤');};
};/* 打開(kāi)或創(chuàng)建數(shù)據(jù)庫(kù)時(shí)發(fā)生錯(cuò)誤 */
request.onerror = function(event) {console.error('打開(kāi)/創(chuàng)建數(shù)據(jù)庫(kù)時(shí)發(fā)生錯(cuò)誤');
};/* 數(shù)據(jù)庫(kù)版本變更 */
request.onupgradeneeded = function(event) {const db = event.target.result;// 創(chuàng)建一個(gè)對(duì)象存儲(chǔ)空間const objectStore = db.createObjectStore('messages', { keyPath: 'id', autoIncrement: true });console.log('數(shù)據(jù)庫(kù)版本已更新');
};