前端實現(xiàn)跨標(biāo)簽頁通信的方案總結(jié)
前情
平時開發(fā)很少有接觸到有什么需求需要實現(xiàn)跨標(biāo)簽頁通信,但最近因為一些變故,不得不重新開始找工作了,其中就有面試官問到一道題,跨標(biāo)簽頁怎么實現(xiàn)數(shù)據(jù)通信,我當(dāng)時只答出二種,面試完后特意重新查資料,因此有些文章
localStorage / sessionStorage
利用localStorage的存儲事件實現(xiàn)通信,當(dāng)一個標(biāo)簽頁修改localStorage時,其他同源標(biāo)簽頁會觸發(fā)storage事件
關(guān)鍵代碼如下:
標(biāo)簽頁1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>page0-localStorage</title>
</head>
<body>
<button id="setCacheBtn">設(shè)置本地緩存</button>
<script>
document.getElementById('setCacheBtn').addEventListener('click', () => {
localStorage.setItem('message', JSON.stringify({
type: 'greeting',
// 這里為了保證通信每次生效,每次都設(shè)置不同的值
content: 'Hello from page0.html' + Date.now()
}));
});
</script>
</body>
</html>
標(biāo)簽頁2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>page1-localStorage</title>
</head>
<body>
<script>
window.addEventListener('storage', (e) => {
if (e.key === 'message') {
const data = JSON.parse(e.newValue);
console.log('Received:page1.html', data);
}
});
</script>
</body>
</html>
動圖演示:

提醒:
- 同源標(biāo)簽才有效
- 設(shè)置不同的本地存儲值才有效,如果設(shè)置的是同一個緩存值是不會生效的
- 兼容性非常nice,可以大膽使用了,國內(nèi)一些瀏覽器從can i use查不到,但兼容性是沒啥問題的
- sessionStorage僅在同一標(biāo)簽頁的不同 iframe 間有效,不適用于不同標(biāo)簽頁

BroadcastChannel
專門用于同源標(biāo)簽頁通信的 API,創(chuàng)建一個頻道后,所有加入該頻道的頁面都能收到消息
關(guān)鍵代碼如下:
標(biāo)簽頁1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BroadcastChannel0</title>
</head>
<body>
<h1>BroadcastChannel0</h1>
<button id="communication">BroadcastChannel0.html 發(fā)送消息</button>
<script>
// 所有頁面都創(chuàng)建相同名稱的頻道
const channel = new BroadcastChannel('my_channel');
// 發(fā)送消息
document.getElementById('communication').addEventListener('click', () => {
channel.postMessage({ type: 'update', data: 'new content from BroadcastChannel0.html' });
});
</script>
</body>
</html>
標(biāo)簽頁2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BroadcastChannel1</title>
</head>
<body>
<h1>BroadcastChannel1</h1>
<script>
// 所有頁面都創(chuàng)建相同名稱的頻道
const channel = new BroadcastChannel('my_channel');
// 接收消息
channel.onmessage = (e) => {
console.log('Received:BroadcastChannel1.html', e.data);
};
</script>
</body>
</html>
演示動圖如下:

提醒:
- 要通信的標(biāo)簽記得都初始化BroadcastChannel,一定記得使用相同的名稱
- 查詢來看除IE外可以大但使用了

SharedWorker
共享工作線程可以在多個標(biāo)簽頁之間共享數(shù)據(jù)和邏輯,通過postMessage通信
關(guān)鍵代碼如下:
標(biāo)簽頁1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SharedWorker0</title>
</head>
<body>
<h1>SharedWorker0</h1>
<button id="communication">SharedWorker0.html 發(fā)送消息</button>
<script>
// 主線程
const worker = new SharedWorker('sw.js');
// 發(fā)送消息
document.getElementById('communication').addEventListener('click', () => {
worker.port.postMessage('Hello from Tab:SharedWorker0.html');
});
</script>
</body>
</html>
標(biāo)簽頁2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SharedWorker1</title>
</head>
<body>
<h1>SharedWorker1</h1>
<script>
// 主線程
const worker = new SharedWorker('sw.js');
// 接收消息
worker.port.onmessage = (e) => {
console.log('Received:SharedWorker1.html', e.data);
};
</script>
</body>
</html>
sw.js關(guān)鍵代碼:
const connections = [];
self.onconnect = (e) => {
const port = e.ports[0];
connections.push(port);
port.onmessage = (e) => {
// 廣播給所有連接的頁面
connections.forEach(p => p.postMessage(e.data));
};
};
動圖演示:

提醒:
- 同源標(biāo)簽才有效
- 不同頁面創(chuàng)建 SharedWorker 時,若指定的腳本路徑不同(即使內(nèi)容相同),會創(chuàng)建不同的 worker 實例
- 頁面與 SharedWorker 之間通過 MessagePort 通信,需通過
port.postMessage()發(fā)送消息,通過port.onmessage接收消息 - SharedWorker 無法訪問 DOM、
window對象或頁面的全局變量,僅能使用 JavaScript 核心 API 和部分 Web API(如fetch、WebSocket) - 兼容性一般,安卓webview全系不兼容
Service Worker
專門用于同源標(biāo)簽頁通信的 API,創(chuàng)建一個頻道后,所有加入該頻道的頁面都能收到消息
關(guān)鍵代碼如下:
標(biāo)簽頁1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ServiceWorker0</title>
</head>
<body>
<h1>ServiceWorker0</h1>
<button id="sendBtn">發(fā)送消息</button>
<script>
// 注冊ServiceWorker
let swReg;
navigator.serviceWorker.register('ServiceWorker.js')
.then(reg => {
swReg = reg;
console.log('SW注冊成功');
});
// 發(fā)送消息
document.getElementById('sendBtn').addEventListener('click', () => {
if (swReg && swReg.active) {
swReg.active.postMessage('來自頁面0的消息');
}
});
</script>
</body>
</html>
標(biāo)簽頁2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ServiceWorker1</title>
</head>
<body>
<h1>ServiceWorker1</h1>
<script>
// 注冊ServiceWorker
navigator.serviceWorker.register('ServiceWorker.js')
.then(() => console.log('SW注冊成功'));
// 接收消息
navigator.serviceWorker.addEventListener('message', (e) => {
console.log('---- Received:ServiceWorker1.html ----:', e.data);
});
</script>
</body>
</html>
ServiceWorker.js關(guān)鍵代碼
// 快速激活
self.addEventListener('install', e => e.waitUntil(self.skipWaiting()));
self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));
// 消息轉(zhuǎn)發(fā)
self.addEventListener('message', e => {
self.clients.matchAll().then(clients => {
clients.forEach(client => {
if (client.id !== e.source.id) {
client.postMessage(e.data);
}
});
});
});
演示動圖如下:

提醒:
Service Worker 要求頁面必須在 HTTPS 環(huán)境 下運行(localhost 除外,方便本地開發(fā)),這是出于安全考慮,防止中間人攻擊篡改 Service Worker 腳本
Service Worker 有嚴(yán)格的生命周期(安裝、激活、空閑、銷毀),一旦注冊成功會長期運行在后臺,更新 Service Worker 需滿足兩個條件:
腳本 URL 不變但內(nèi)容有差異
需在 install 事件中調(diào)用 self.skipWaiting(),并在 activate 事件中調(diào)用 self.clients.claim() 讓新 Worker 立即生效
Service Worker 的作用域由注冊路徑?jīng)Q定,默認只能控制其所在路徑及子路徑下的頁面,例如:/sw.js 可控制全站,/js/sw.js 默認只能控制 /js/ 路徑下的頁面,可通過 scope 參數(shù)指定作用域,但不能超出注冊文件所在路徑的范圍
可在瀏覽器開發(fā)者工具的 Application > Service Workers 面板進行調(diào)試,
- 查看當(dāng)前運行的 Service Worker 狀態(tài)
- 強制更新、停止或注銷 Worker
- 模擬離線環(huán)境
主流瀏覽器都支持,使用的時候可以通過Is service worker ready?,測試兼容性
window.open + window.opener
如果標(biāo)簽頁是通過window.open打開的,可以直接通過opener屬性通信
父窗口,打開子窗口的頁面關(guān)鍵代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>parent</title>
</head>
<body>
<h1>window.open parent</h1>
<button id="openBtn">打開子窗口</button>
<button id="sendBtn">發(fā)送消息</button>
<div id="messageDisplay"></div>
<script>
let childWindow = null;
let messageHandler = null;
// 打開子窗口
document.getElementById('openBtn').addEventListener('click', () => {
// 如果已有窗口,先關(guān)閉
if (childWindow && !childWindow.closed) {
childWindow.close();
}
childWindow = window.open('./children.html', 'childWindow');
});
// 發(fā)送消息
document.getElementById('sendBtn').addEventListener('click', () => {
if (childWindow && !childWindow.closed) {
// window.location.origin限制接收域名
childWindow.postMessage('Hello child', window.location.origin);
} else {
alert('請先打開子窗口');
}
});
// 接收子窗口的消息
messageHandler = (e) => {
if (e.origin === window.location.origin && e.source !== window) {
document.getElementById('messageDisplay').textContent = '收到消息: ' + e.data;
console.log('父頁面收到消息:', e.data);
}
};
window.addEventListener('message', messageHandler);
</script>
</body>
</html>
通過window.open打開的子頁面關(guān)鍵代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>children</title>
</head>
<body>
<h1>子窗口</h1>
<button id="replyBtn">回復(fù)父窗口</button>
<div id="messageDisplay"></div>
<script>
let messageHandler = null;
// 只在頁面加載完成后設(shè)置消息監(jiān)聽
window.onload = function() {
// 接收父頁面消息
messageHandler = (e) => {
if (e.origin === window.location.origin && e.source !== window) {
console.log('子頁面收到消息:', e.data);
// 顯示收到的消息
document.getElementById('messageDisplay').textContent = '收到消息: ' + e.data;
window.opener.postMessage('子窗口已收到消息', e.origin);
}
};
window.addEventListener('message', messageHandler);
};
// 手動回復(fù)按鈕
document.getElementById('replyBtn').addEventListener('click', () => {
if (window.opener) {
window.opener.postMessage('來自子窗口的回復(fù)', window.location.origin);
}
});
</script>
</body>
</html>
提醒:
- 允許跨域通信,但必須由開發(fā)者顯式指定信任的源,避免惡意網(wǎng)站濫用
- 在事件監(jiān)聽的時候記得判斷e.source,避免自己發(fā)送的事件自己接收了
- 若子窗口被關(guān)閉,父窗口中對它的引用(如
childWindow)會變成無效對象,調(diào)用其方法會報錯 - window.open使用會有一些限制,最好是在事件中使用,有的瀏覽器還會有權(quán)限提示,需要用戶同意才行,若
window.open被瀏覽器攔截(非用戶主動觸發(fā)),會返回null,導(dǎo)致后續(xù)通信失敗
總結(jié)
面試官有提到Service Worker也可以,我面試完后的查詢資料嘗試了這些方法,都挺順利的,就是Service Worker折騰了一會才跑通,使用起來相比前面的一些方式,它稍微復(fù)雜一些,我覺得用于消息通信只是它的冰山一角,它有一個主要功能就是用來解決一些耗性能的計算密集任務(wù)
個人技術(shù)有限,如果你有更好的跨標(biāo)簽頁通信方式,期待你的分享,你工作中有遇到這種跨標(biāo)簽頁通信的需求么,如果有你用的是哪一種了,期待你的留言
以上就是前端實現(xiàn)跨標(biāo)簽頁通信的方案總結(jié)的詳細內(nèi)容,更多關(guān)于前端跨標(biāo)簽頁通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript實現(xiàn)網(wǎng)頁電子時鐘
這篇文章主要為大家詳細介紹了JavaScript實現(xiàn)網(wǎng)頁電子時鐘,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06
JavaScript實現(xiàn)審核流程狀態(tài)的動態(tài)顯示進度條
對于有很多流程的東西,我們希望能夠根據(jù)不同的階段,用流程條對應(yīng)地進行顯示,非常直觀,給用戶帶來極好的用戶體驗,下面小編給大家分享JavaScript實現(xiàn)審核流程狀態(tài)的動態(tài)顯示進度條功能,需要的的朋友參考下2017-03-03
javascript對象的相關(guān)操作小結(jié)
下面小編就為大家?guī)硪黄猨avascript對象的相關(guān)操作小結(jié)。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-05-05

