当前位置: 首页 > news >正文

前端跨标签页通信方案(下)

前情

平时开发很少有接触到有什么需求需要实现跨标签页通信,但最近因为一些变故,不得不重新开始找工作了,其中就有面试官问到一道题,跨标签页怎么实现数据通信,我当时只答出二种,面试完后特意重新查资料,因此有些文章

SharedWorker

共享工作线程可以在多个标签页之间共享数据和逻辑,通过postMessage通信

关键代码如下:

标签页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 发送消息</button><script>// 主线程const worker = new SharedWorker('sw.js');// 发送消息document.getElementById('communication').addEventListener('click', () => {worker.port.postMessage('Hello from Tab:SharedWorker0.html');});</script>
</body>
</html>

标签页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关键代码:

const connections = [];self.onconnect = (e) => {const port = e.ports[0];connections.push(port);port.onmessage = (e) => {// 广播给所有连接的页面connections.forEach(p => p.postMessage(e.data));};
};

动图演示:

20250923_203404

提醒:

  • 同源标签才有效
  • 不同页面创建 SharedWorker 时,若指定的脚本路径不同(即使内容相同),会创建不同的 worker 实例
  • 页面与 SharedWorker 之间通过 MessagePort 通信,需通过 port.postMessage() 发送消息,通过 port.onmessage 接收消息
  • SharedWorker 无法访问 DOM、window 对象或页面的全局变量,仅能使用 JavaScript 核心 API 和部分 Web API(如 fetchWebSocket
  • 兼容性一般,安卓webview全系不兼容

Service Worker

专门用于同源标签页通信的 API,创建一个频道后,所有加入该频道的页面都能收到消息

关键代码如下:

标签页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">发送消息</button><script>// 注册ServiceWorkerlet swReg;navigator.serviceWorker.register('ServiceWorker.js').then(reg => {swReg = reg;console.log('SW注册成功');});// 发送消息document.getElementById('sendBtn').addEventListener('click', () => {if (swReg && swReg.active) {swReg.active.postMessage('来自页面0的消息');}});</script>
</body>
</html>

标签页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>// 注册ServiceWorkernavigator.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关键代码

// 快速激活
self.addEventListener('install', e => e.waitUntil(self.skipWaiting()));
self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));// 消息转发
self.addEventListener('message', e => {self.clients.matchAll().then(clients => {clients.forEach(client => {if (client.id !== e.source.id) {client.postMessage(e.data);}});});
});

演示动图如下:

20250923_212126

提醒:

  • Service Worker 要求页面必须在 HTTPS 环境 下运行(localhost 除外,方便本地开发),这是出于安全考虑,防止中间人攻击篡改 Service Worker 脚本
  • Service Worker 有严格的生命周期(安装、激活、空闲、销毁),一旦注册成功会长期运行在后台,更新 Service Worker 需满足两个条件:
  1. 脚本 URL 不变但内容有差异
  2. 需在 install 事件中调用 self.skipWaiting(),并在 activate 事件中调用 self.clients.claim() 让新 Worker 立即生效
  • Service Worker 的作用域由注册路径决定,默认只能控制其所在路径及子路径下的页面,例如:/sw.js 可控制全站,/js/sw.js 默认只能控制 /js/ 路径下的页面,可通过 scope 参数指定作用域,但不能超出注册文件所在路径的范围
  • 可在浏览器开发者工具的 Application > Service Workers 面板进行调试,
    • 查看当前运行的 Service Worker 状态
    • 强制更新、停止或注销 Worker
    • 模拟离线环境
  • 主流浏览器都支持,使用的时候可以通过Is service worker ready?,测试兼容性

window.open + window.opener

如果标签页是通过window.open打开的,可以直接通过opener属性通信
父窗口,打开子窗口的页面关键代码:

<!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">发送消息</button><div id="messageDisplay"></div><script>let childWindow = null;let messageHandler = null;// 打开子窗口document.getElementById('openBtn').addEventListener('click', () => {// 如果已有窗口,先关闭if (childWindow && !childWindow.closed) {childWindow.close();}childWindow = window.open('./children.html', 'childWindow');});// 发送消息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打开的子页面关键代码:

<!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">回复父窗口</button><div id="messageDisplay"></div><script>let messageHandler = null;// 只在页面加载完成后设置消息监听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);};// 手动回复按钮document.getElementById('replyBtn').addEventListener('click', () => {if (window.opener) {window.opener.postMessage('来自子窗口的回复', window.location.origin);}});</script>
</body>
</html>

提醒:

  • 允许跨域通信,但必须由开发者显式指定信任的源,避免恶意网站滥用
  • 在事件监听的时候记得判断e.source,避免自己发送的事件自己接收了
  • 若子窗口被关闭,父窗口中对它的引用(如 childWindow)会变成无效对象,调用其方法会报错
  • window.open使用会有一些限制,最好是在事件中使用,有的浏览器还会有权限提示,需要用户同意才行,若 window.open 被浏览器拦截(非用户主动触发),会返回 null,导致后续通信失败

总结

面试官有提到Service Worker也可以,我面试完后的查询资料尝试了这些方法,都挺顺利的,就是Service Worker折腾了一会才跑通,使用起来相比前面的一些方式,它稍微复杂一些,我觉得用于消息通信只是它的冰山一角,它有一个主要功能就是用来解决一些耗性能的计算密集任务

个人技术有限,如果你有更好的跨标签页通信方式,期待你的分享,你工作中有遇到这种跨标签页通信的需求么,如果有你用的是哪一种了,期待你的留言

http://www.jsqmd.com/news/45041/

相关文章:

  • js 如何debug SharedWorker
  • 代码随想录Day15_二叉树
  • 2025农膜厂商最新top推荐:三光膜/ 大棚膜/水池布优质供应商
  • 什么是代币?从ERC-20开始 - all-in
  • NCHU-OOP-前三次大作业总结 - AC
  • Yanhua Mini ACDP-2 BMW CAS Package: Advanced CAS ISN Module Programming for N20/N55/B38
  • NCHU-OO-前三次大作业总结 - AC
  • Postman关于AES的加解密
  • 汉诺塔问题详解
  • 20232307 2025-2026-1 《网络与系统攻防技术》实验七实验报告
  • 《R语言医学数据分析实战》学习记录--第一章 R语言介绍
  • 251119明天就要去适应比赛场地了
  • 【数据结构】哈希表的理论与实现 - 教程
  • pip安装第三方包
  • 李克特量表(Likert scale)
  • java---maven
  • 新来的外包,在大群分享了它的限流算法的实现
  • 状语从句学案
  • 用 Rust 与 Tesseract 进行英文数字验证码识别
  • 详细介绍:开源AI大模型、AI智能名片与S2B2C商城系统:个体IP打造与价值赋能的新范式
  • ThreadLocal 源码解析
  • 黑马程序员SpringCloud微服务开发与实战- Docker项目部署-03
  • C# 和 Tesseract 实现英文数字验证码识别
  • contig 和 scaffold的区别和联系
  • linux ftp自动
  • linux ftp脚本
  • 实用指南:【案例实战】鸿蒙分布式智能办公应用的架构设计与性能优化
  • Yanhua Mini ACDP-2 BMW ECU Package: EUC Clone License with Modules 3/8/27 Bench Interface Board
  • Yanhua Mini ACDP-2 BMW ECU Package: EUC Clone License with Modules 3/8/27 Bench Interface Board
  • 根据图片路径将文件下载到本地