乐闻世界logo
搜索文章和话题

SharedWorker 如何实现跨标签页通信?

2月21日 15:24

SharedWorker 允许多个浏览器上下文(如不同的标签页、iframe)共享同一个 Worker 实例,实现跨页面通信和状态共享。

SharedWorker 的核心概念

特点

  • 多个页面可以连接到同一个 SharedWorker
  • 使用 port 对象进行通信
  • 通过 onconnect 事件处理新连接
  • 适合需要跨标签页同步的场景

基本使用

创建 SharedWorker

javascript
// 主线程代码 const sharedWorker = new SharedWorker('shared-worker.js'); // 启动端口连接 sharedWorker.port.start(); // 发送消息 sharedWorker.port.postMessage('Hello from page 1'); // 接收消息 sharedWorker.port.onmessage = function(event) { console.log('Received:', event.data); };

SharedWorker 脚本实现

javascript
// shared-worker.js const connections = []; // 监听新连接 self.onconnect = function(event) { const port = event.ports[0]; connections.push(port); // 监听来自该端口的消息 port.onmessage = function(e) { console.log('Received from port:', e.data); // 广播消息给所有连接 connections.forEach(conn => { if (conn !== port) { conn.postMessage(e.data); } }); }; // 启动端口 port.start(); };

实际应用场景

1. 跨标签页状态同步

javascript
// shared-worker.js let sharedState = { user: null, theme: 'light', notifications: [] }; self.onconnect = function(event) { const port = event.ports[0]; // 发送当前状态给新连接 port.postMessage({ type: 'init', state: sharedState }); port.onmessage = function(e) { if (e.data.type === 'updateState') { sharedState = { ...sharedState, ...e.data.state }; // 广播状态更新给所有连接 connections.forEach(conn => { conn.postMessage({ type: 'stateUpdated', state: sharedState }); }); } }; connections.push(port); port.start(); };

2. 跨标签页聊天

javascript
// shared-worker.js const connections = []; const messages = []; self.onconnect = function(event) { const port = event.ports[0]; connections.push(port); // 发送历史消息 port.postMessage({ type: 'history', messages }); port.onmessage = function(e) { const message = { text: e.data.text, user: e.data.user, timestamp: Date.now() }; messages.push(message); // 广播新消息给所有连接 connections.forEach(conn => { conn.postMessage({ type: 'newMessage', message }); }); }; port.start(); };

3. 共享计数器

javascript
// shared-worker.js let counter = 0; self.onconnect = function(event) { const port = event.ports[0]; // 发送当前计数 port.postMessage({ type: 'counter', value: counter }); port.onmessage = function(e) { if (e.data.type === 'increment') { counter++; } else if (e.data.type === 'decrement') { counter--; } else if (e.data.type === 'reset') { counter = 0; } // 广播更新后的计数 connections.forEach(conn => { conn.postMessage({ type: 'counter', value: counter }); }); }; connections.push(port); port.start(); };

高级用法

1. 连接管理

javascript
// shared-worker.js const connections = new Map(); // 使用 Map 管理连接 self.onconnect = function(event) { const port = event.ports[0]; const connectionId = Date.now() + Math.random(); connections.set(connectionId, port); port.onmessage = function(e) { if (e.data.type === 'ping') { port.postMessage({ type: 'pong', id: connectionId }); } }; port.onclose = function() { connections.delete(connectionId); console.log('Connection closed:', connectionId); }; port.start(); };

2. 广播和定向消息

javascript
// shared-worker.js const connections = new Map(); self.onconnect = function(event) { const port = event.ports[0]; const connectionId = Date.now().toString(); connections.set(connectionId, port); port.postMessage({ type: 'connected', id: connectionId }); port.onmessage = function(e) { if (e.data.type === 'broadcast') { // 广播给所有连接 connections.forEach((conn, id) => { conn.postMessage({ type: 'broadcast', from: connectionId, message: e.data.message }); }); } else if (e.data.type === 'sendTo') { // 定向发送给特定连接 const targetPort = connections.get(e.data.targetId); if (targetPort) { targetPort.postMessage({ type: 'private', from: connectionId, message: e.data.message }); } } }; port.start(); };

3. 持久化状态

javascript
// shared-worker.js let sharedState = {}; // 从 localStorage 加载初始状态 try { const savedState = localStorage.getItem('sharedWorkerState'); if (savedState) { sharedState = JSON.parse(savedState); } } catch (e) { console.error('Failed to load state:', e); } function saveState() { try { localStorage.setItem('sharedWorkerState', JSON.stringify(sharedState)); } catch (e) { console.error('Failed to save state:', e); } } self.onconnect = function(event) { const port = event.ports[0]; port.postMessage({ type: 'init', state: sharedState }); port.onmessage = function(e) { if (e.data.type === 'update') { sharedState = { ...sharedState, ...e.data.state }; saveState(); // 广播更新 connections.forEach(conn => { conn.postMessage({ type: 'stateUpdated', state: sharedState }); }); } }; connections.push(port); port.start(); };

注意事项

1. 端口必须启动

javascript
// ❌ 忘记启动端口 const sharedWorker = new SharedWorker('worker.js'); sharedWorker.port.postMessage('Hello'); // 可能不会发送 // ✅ 正确启动端口 const sharedWorker = new SharedWorker('worker.js'); sharedWorker.port.start(); sharedWorker.port.postMessage('Hello');

2. 消息是异步的

javascript
// 消息不会立即到达 sharedWorker.port.postMessage('message1'); sharedWorker.port.postMessage('message2'); // 需要通过 onmessage 接收响应

3. 连接断开处理

javascript
// Worker 端 port.onclose = function() { console.log('Port closed'); // 清理资源 }; // 主线程端 sharedWorker.port.close();

与 Dedicated Worker 的对比

特性Dedicated WorkerShared Worker
作用域单页面多页面共享
连接数1:11:N
通信方式postMessageport.postMessage
适用场景单页面后台任务跨页面通信

最佳实践

  1. 连接管理:使用 Map 或数组管理所有连接
  2. 错误处理:在连接和消息处理中添加错误处理
  3. 状态同步:新连接时发送当前状态
  4. 资源清理:连接断开时清理相关资源
  5. 消息格式:使用统一的消息格式(如 { type, data }
标签:Web Worker