【面試題】2057- 京東一面:瀏覽器跨標簽頁通信的方式都有什么?
共 18955字,需瀏覽 38分鐘
·
2024-05-25 14:29
跨標簽通信也有很多實際的應(yīng)用場景,比如:
-
共享登錄狀態(tài):當用戶在一個標簽頁中登錄后,其他打開的標簽頁需要及時獲取到登錄狀態(tài),以保持一致的用戶體驗。在這種情況下,可以使用瀏覽器的localStorage或sessionStorage來存儲登錄狀態(tài),并通過監(jiān)聽storage事件來實現(xiàn)不同標簽頁之間的狀態(tài)同步。
-
實時通知和消息推送:如果用戶在一個標簽頁上收到了新消息或通知,可以通過跨標簽頁通信將該消息或通知傳遞給其他標簽頁。一種常見的處理方式是使用瀏覽器的localStorage或IndexedDB來存儲未讀消息或通知,然后通過監(jiān)聽storage事件或定時輪詢來檢查新消息或通知的變化。
-
跨標簽頁數(shù)據(jù)共享:有時候需要在不同的標簽頁之間共享一些數(shù)據(jù),例如購物車數(shù)據(jù)、選項設(shè)置等。這可以通過在localStorage或IndexedDB中存儲數(shù)據(jù),并借助storage事件或定時輪詢來實現(xiàn)數(shù)據(jù)的同步更新。
-
標簽頁之間的導航同步:當用戶在一個標簽頁中進行導航操作(例如點擊鏈接或提交表單)時,其他標簽頁可能也需要跟隨導航到相應(yīng)的頁面。這可以通過在標簽頁之間發(fā)送消息或共享狀態(tài)來實現(xiàn)導航的同步。
在前端中處理瀏覽器跨標簽頁通信時,常用的方法包括:
-
使用localStorage或sessionStorage存儲共享數(shù)據(jù),并通過監(jiān)聽storage事件來實現(xiàn)數(shù)據(jù)的變化檢測和同步更新。 -
使用BroadcastChannel API,它提供了一種跨窗口通信的機制,可以在不同標簽頁之間發(fā)送消息。 -
使用window.postMessage()方法,該方法允許在不同的窗口或標簽頁之間安全地傳遞消息。 -
借助服務(wù)端的實時通信技術(shù),如WebSocket,通過服務(wù)器作為中介來實現(xiàn)標簽頁之間的消息傳遞和數(shù)據(jù)同步。
本篇將為你帶來關(guān)于跨瀏覽器標簽通信的詳細分享,以下是正文:
沒錯,還是京東一面的問題,首先問的是瀏覽器跨標簽也通信的方式有什么,我答完瀏覽器通信通信的方式,后面就接著問 JavaScript 有什么方式的了。
瀏覽器通信方式
每個瀏覽器標簽頁通常被視為一個獨立的進程,而不是一個線程。這種多進程架構(gòu)被稱之為多進程瀏覽器,谷歌瀏覽器就是采用這種方式。
這種架構(gòu)的方式的主要目的是提高瀏覽器的穩(wěn)定性、安全性和性能。
在多進程瀏覽器中,每個標簽頁都獨立運行在獨立的進程中,這樣一旦一個標簽頁崩潰或遇到問題,不會影響其他標簽頁和瀏覽器本身的穩(wěn)定性。而每個進程都有屬于自己的內(nèi)存。
在多進程瀏覽器中,不同標簽頁之間的通信是通過進程間通信 IPC 機制來實現(xiàn)的。IPC 是操作系統(tǒng)提供的一種機制,允許不同進程之間交換數(shù)據(jù)和消息,從而實現(xiàn)協(xié)同工作。
在操作系統(tǒng)中,著有有以下幾種通信方式:
-
基于管道的通信: -
管道是一種半雙工的通信機制,可用于同一父進程與其子進程之間通信,或者用于同一計算機上的不同進程之間通信。 -
命名管道提供了進程間進行雙向通信的能力。可以被多個進程打開和使用。其中一個進程將數(shù)據(jù)寫入管道,而另一個進程則可以從管道中讀取這些數(shù)據(jù)。命名管道通常用于在不相關(guān)的進程之間傳遞數(shù)據(jù),比如客戶端和服務(wù)器之間的通信。 -
匿名管道是一種用于單向通信的機制,僅用于具有父子關(guān)系的進程之間。它只能在創(chuàng)建時通過操作系統(tǒng)提供的機制進行傳遞。匿名管道在創(chuàng)建時自動建立,并且只能用于具有親緣關(guān)系的進程之間的通信。其中一個進程將數(shù)據(jù)寫入管道的寫端,而另一個進程則從管道的讀端讀取這些數(shù)據(jù)。 -
消息隊列:消息隊列允許進程通過將消息放入隊列中來進行通信。進程可以從隊列中接收消息,實現(xiàn)異步通信。消息隊列適用于不需要直接的點對點連接的場景,而且可以在不同計算機之間通信。 -
共享內(nèi)存:共享內(nèi)存允許多個進程訪問同一塊物理內(nèi)存區(qū)域,從而實現(xiàn)高效的數(shù)據(jù)共享。進程可以在共享內(nèi)存中讀寫數(shù)據(jù),而不需要顯式的數(shù)據(jù)傳輸操作。 -
套接字 Socket:套接字通信是一種在計算機網(wǎng)絡(luò)中實現(xiàn)進程間通信的方式。它基于網(wǎng)絡(luò)協(xié)議棧,使用 TCP 或 UDP 等傳輸層協(xié)議,在不同的主機之間進行數(shù)據(jù)傳輸和通信。 -
Remote Procedure Call:RPC 允許一個進程通過網(wǎng)絡(luò)請求調(diào)用另一個進程中的函數(shù),就像調(diào)用本地函數(shù)一樣。遠程過程調(diào)用隱藏了底層通信細節(jié),使得進程間通信更加方便。 -
信號(Signal):信號通信是一種在操作系統(tǒng)中實現(xiàn)進程間通信的機制。它允許一個進程向另一個進程發(fā)送信號,用于通知、中斷或請求處理等目的。它是一種異步事件,當某個事件發(fā)生時,操作系統(tǒng)會向進程發(fā)送相應(yīng)的信號。進程可以事先注冊信號處理函數(shù)來捕獲并處理這些信號。
JavaScript 如何實現(xiàn)跨標簽頁通信
JavaScript 實現(xiàn)跨標簽頁通信的方式有很多中,接下來我們就來一個一個進行學習。
BroadcastChannel
BroadcastChannel 通信的方式原理就是一個命名管道。它允許讓指定的同源下瀏覽器不同的窗口來訂閱它。
每個 BroadcastChannel 對象都需要使用一個唯一的名稱來標識通道,這個名稱在同一域名下的不同頁面之間必須是唯一的。它允許同一域名下的不同頁面之間進行通信。
通過 postMessage 方法,一個頁面可以將消息發(fā)送到頻道中,而其他頁面則可以監(jiān)聽 message 事件來接收這些消息。通過這種方式是短線了一種實時通信的機制,可以在不同的頁面之間傳遞信息,實現(xiàn)頁面間的即時交流。如下圖所示:
BroadcastChannel 的類型定義有如下代碼所示:
[Exposed=(Window,Worker)]
interface BroadcastChannel : EventTarget {
constructor(DOMString name);
readonly attribute DOMString name;
undefined postMessage(any message);
undefined close();
attribute EventHandler onmessage;
attribute EventHandler onmessageerror;
};
要想使用,首先我們創(chuàng)建兩個不同的 html 文件分別代表不同的頁面,并且使用 live server 開啟一個本地服務(wù)器:
<!-- a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const broad = new BroadcastChannel("moment");
setInterval(() => {
broad.postMessage({
value: `moment ${new Date()}`,
});
}, 3000);
broad.onmessage = function (e) {
console.log(e.data);
};
</script>
</body>
</html>
<!-- b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
const broad = new BroadcastChannel("moment");
let index = 1;
setInterval(() => {
broad.postMessage({
value: `supper ${index++}`,
});
}, 3000);
broad.onmessage = function (e) {
console.log(e.data);
};
</script>
</body>
</html>
代碼編寫完了,我們來看看代碼允許效果,如下圖所示:
通過 postMessage 向管道中發(fā)送消息,當管道中存在消息的時候,可以通過 onmessage 方法獲取到信息內(nèi)容。
Service Worker
Service Worker 它是一種服務(wù)工作線程,是一種在瀏覽器背后運行的腳本,用于處理網(wǎng)絡(luò)請求和緩存等任務(wù)。它是一種在瀏覽器與網(wǎng)絡(luò)之間的中間層,允許開發(fā)者攔截和控制頁面發(fā)出的網(wǎng)絡(luò)請求,以及管理緩存,從而實現(xiàn)離線訪問、性能優(yōu)化和推送通知等功能。
它在瀏覽器背后獨立運行與網(wǎng)頁分開,這意味著即使用戶關(guān)閉了網(wǎng)頁,Service Worker 仍然可以運行。可以用于實現(xiàn)推送通知功能。它可以注冊為推送消息的接收者,當服務(wù)器有新的通知要發(fā)送時,Service Worker 可以顯示通知給用戶,即使網(wǎng)頁沒有打開。
要想使用,首先我們創(chuàng)建兩個不同的 html 文件分別代表不同的頁面,創(chuàng)建一個 Service Worker 文件,并且使用 live server 開啟一個本地服務(wù)器:
<!-- a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
navigator.serviceWorker.register("worker.js").then(() => {
console.log("注冊成功");
});
setInterval(() => {
navigator.serviceWorker.controller.postMessage({
value: `moment ${new Date()}`,
});
}, 3000);
navigator.serviceWorker.onmessage = function (e) {
console.log(e.data.value);
};
</script>
</body>
</html>
<!-- b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
navigator.serviceWorker.register("worker.js").then(() => {
console.log("注冊成功");
});
setInterval(() => {
navigator.serviceWorker.controller.postMessage({
value: `moment ${new Date()}`,
});
}, 3000);
navigator.serviceWorker.onmessage = function (e) {
console.log(e.data.value);
};
</script>
</body>
</html>
創(chuàng)建一個 worker.js 文件并編寫以下代碼:
// worker.js
self.addEventListener("message", function (e) {
e.waitUntil(
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
client.postMessage(e.data);
});
})
);
});
你所編寫的 Service Worker 將遵守以下生命周期:
-
注冊: 在網(wǎng)頁的 JavaScript 代碼中調(diào)用 navigator.serviceWorker.register() 方法來注冊一個 Service Worker;
-
安裝: 當 Service Worker 文件被下載并首次運行時,會觸發(fā) install 事件。在 install 事件中,你可以緩存靜態(tài)資源,如 HTML、CSS、JavaScript 文件,以便在離線時使用;
-
激活: 安裝成功后,Service Worker 并不會立即接管頁面的網(wǎng)絡(luò)請求。它需要等到之前的所有頁面都關(guān)閉,或者在下次頁面加載時才會激活();
就是因為編寫的 worker 代碼不生效,一直刷新都不生效,直到我關(guān)機重啟了才生效的......
-
控制: 一旦 Service Worker 被激活,它就開始控制在其作用域內(nèi)的頁面。它可以攔截頁面發(fā)出的網(wǎng)絡(luò)請求,并根據(jù)緩存策略返回緩存的內(nèi)容;
-
更新: 當你更新 Service Worker 文件并再次注冊時,會觸發(fā)一個新的 install 事件。你可以在新的 install 事件中更新緩存,然后在下次頁面加載時進行激活,以確保新的 Service Worker 被使用;
-
解除注冊: 如果你不再需要 Service Worker,可以通過調(diào)用
navigator.serviceWorker.unregister()來解除注冊;
它本身是一個由 promise 封裝的對象,未初始化時是一個 pending 狀態(tài)的,當成功注冊之后會變成 fulfilled,并且對外暴露以下方法,如下圖所示:
localStorage
在 Web Storage 中,每一次將一個值存儲到本地存儲時,都會觸發(fā)一個 storage 事件,由事件監(jiān)聽器發(fā)送給回調(diào)函數(shù)的事件對象有如下圖所示:
其中有幾個自動填充的屬性如下:
-
key:被修改的鍵; -
newValue:修改后的新值; -
oldValue:修改前的值; -
storageArea:事件監(jiān)聽對應(yīng)的 Storage 對象
編寫如下代碼,省略部分代碼:
<!-- a.html -->
<script>
let index = 0;
setInterval(() => {
localStorage.moment = `moment ${index++}`;
}, 1000);
window.addEventListener("storage", (e) => {
console.log("被修改的鍵: ", e.key);
console.log("舊值: ", e.oldValue);
console.log("新值: ", e.newValue);
});
</script>
<!-- b.html -->
<script>
let index = 0;
setInterval(() => {
localStorage.supper = `supper ${index++}`;
}, 1000);
window.addEventListener("storage", (e) => {
console.log("被修改的鍵: ", e.key);
console.log("舊值: ", e.oldValue);
console.log("新值: ", e.newValue);
console.log("----------");
});
</script>
SharedWorker
SharedWorker 是一種在 Web 瀏覽器中使用的 Web API,它允許不同的瀏覽上下文,如不同的瀏覽器標簽頁之間共享數(shù)據(jù)和執(zhí)行代碼。它可以用于在多個瀏覽上下文之間建立通信通道,以便它們可以共享信息和協(xié)同工作。
與普通的 Worker 不同,SharedWorker 可以在多個瀏覽上下文中實例化,而不僅限于一個單獨的瀏覽器標簽頁或框架。這使得多個瀏覽上下文可以共享同一個后臺線程,從而更有效地共享數(shù)據(jù)和資源,而不必在每個標簽頁或框架中都創(chuàng)建一個獨立的工作線程。
要想使用它,首先編寫如下代碼,省略部分代碼:
<!-- a.html -->
<script>
let index = 0;
const worker = new SharedWorker("worker.js");
setInterval(() => {
worker.port.postMessage(`moment ${index++}`);
}, 1000);
</script>
<!-- b.html -->
<script>
const worker = new SharedWorker("worker.js");
worker.port.start();
setInterval(() => {
worker.port.postMessage("php是世界上最好的語言");
}, 1000);
worker.port.onmessage = function (e) {
if (e.data) {
console.log(e.data);
}
};
</script>
創(chuàng)建一個 worker.js 文件,并編寫以下代碼:
let data = "";
self.onconnect = (e) => {
const port = e.ports[0];
port.onmessage = function (e) {
if (e.data === "php是世界上最好的語言") {
port.postMessage(data);
data = "";
} else {
data = e.data;
}
};
};
IndexDB
IndexedDB 是一種在瀏覽器中用于存儲和管理大量結(jié)構(gòu)化數(shù)據(jù)的 Web API。它提供了一種持久性存儲解決方案,允許 Web 應(yīng)用程序在客戶端存儲數(shù)據(jù),以便在不同會話、頁面加載或瀏覽器關(guān)閉之間保留數(shù)據(jù)。
與傳統(tǒng)的 cookie 或 localStorage 等存儲方式不同,IndexedDB 更適合存儲復(fù)雜的、結(jié)構(gòu)化的數(shù)據(jù),例如對象、數(shù)組、鍵值對等。這使得它特別適用于應(yīng)用程序需要存儲大量數(shù)據(jù)、執(zhí)行高級查詢或支持離線工作的情況。
要實現(xiàn)跨標簽通信,如下代碼所示:
<!-- a.html -->
<script>
let index = 0;
// 打開或創(chuàng)建 IndexedDB 數(shù)據(jù)庫
const request = indexedDB.open("database", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const objectStore = db.createObjectStore("dataStore", {
keyPath: "key",
});
};
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["dataStore"], "readwrite");
const objectStore = transaction.objectStore("dataStore");
// 存儲數(shù)據(jù)
objectStore.put({ key: "supper", value: `moment` });
transaction.oncomplete = () => {
db.close();
};
};
</script>
<!-- b.html -->
<script>
// 打開相同的 IndexedDB 數(shù)據(jù)庫
const request = indexedDB.open("database", 1);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(["dataStore"], "readonly");
const objectStore = transaction.objectStore("dataStore");
// 獲取數(shù)據(jù)
const getRequest = objectStore.get("supper");
getRequest.onsuccess = (event) => {
const data = event.target.result;
if (data) {
console.log(data.value);
}
};
transaction.oncomplete = () => {
db.close();
};
};
</script>
最終代碼運行如下圖所示:
cookie
cookie 的話沒什么好講的,直接上代吧:
<!-- a.html -->
<script>
let index = 0;
setInterval(() => {
document.cookie = `supper=moment ${index++}`;
}, 1000);
</script>
<!-- b.html -->
<script>
console.log("cookie 的值為: ", document.cookie);
setInterval(() => {
console.log("cookie 的值發(fā)生了變化: ", document.cookie);
}, 1000);
</script>
具體代碼運行效果如下圖所示:
postMessage
window.postMessage() 方法可以安全地實現(xiàn)跨源通信。通常,對于兩個不同頁面的腳本,只有同源時,這兩個腳本才能相互通信。
<!-- a.html -->
<body>
<button class="pop">彈出新窗口</button>
<button class="button">發(fā)送數(shù)據(jù)</button>
<script>
const pop = document.querySelector(".pop");
const button = document.querySelector(".button");
let index = 0;
let opener = null;
pop.addEventListener("click", () => {
opener = window.open(
"b.html",
"123",
"height=600,width=600,top=20,resizeable=yes"
);
});
button.addEventListener("click", () => {
const data = {
value: `moment ${index++}`,
};
opener.postMessage(data, "*");
});
</script>
</body>
<!-- b.html -->
<body>
<div>moment</div>
<script>
window.addEventListener("message", (e) => {
console.log(e.data);
});
</script>
</body>
通過點擊按鈕在主窗口和彈出的新窗口之間進行通信。通過 postMessage,主窗口可以向新窗口發(fā)送數(shù)據(jù),從而實現(xiàn)了簡單的跨窗口通信。在實際應(yīng)用中,你可以在接收消息的窗口中監(jiān)聽 message 事件,然后在事件處理程序中處理接收到的數(shù)據(jù)。
總結(jié)
文章中涉及到的大部分名詞解釋來自 mdn。
JavaScript 跨標簽通信允許不同的瀏覽器標簽頁之間進行數(shù)據(jù)傳遞和通信,為構(gòu)建更復(fù)雜和協(xié)同的 Web 應(yīng)用程序提供了一種機制。通過適當?shù)募夹g(shù)和策略,我們可以實現(xiàn)有效的跨標簽通信,以滿足不同應(yīng)用場景的需求。
原文地址:https://juejin.cn/post/7270155117705510968
作者:Moment
