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

How does SharedWorker implement cross-tab communication?

2月21日 15:24

SharedWorker allows multiple browser contexts (such as different tabs, iframes) to share the same Worker instance, enabling cross-page communication and state sharing.

Core Concepts of SharedWorker

Characteristics

  • Multiple pages can connect to the same SharedWorker
  • Uses port object for communication
  • Handles new connections through onconnect event
  • Suitable for scenarios requiring cross-tab synchronization

Basic Usage

Creating SharedWorker

javascript
// Main thread code const sharedWorker = new SharedWorker('shared-worker.js'); // Start port connection sharedWorker.port.start(); // Send message sharedWorker.port.postMessage('Hello from page 1'); // Receive message sharedWorker.port.onmessage = function(event) { console.log('Received:', event.data); };

SharedWorker Script Implementation

javascript
// shared-worker.js const connections = []; // Listen for new connections self.onconnect = function(event) { const port = event.ports[0]; connections.push(port); // Listen for messages from this port port.onmessage = function(e) { console.log('Received from port:', e.data); // Broadcast message to all connections connections.forEach(conn => { if (conn !== port) { conn.postMessage(e.data); } }); }; // Start port port.start(); };

Practical Use Cases

1. Cross-Tab State Synchronization

javascript
// shared-worker.js let sharedState = { user: null, theme: 'light', notifications: [] }; self.onconnect = function(event) { const port = event.ports[0]; // Send current state to new connection port.postMessage({ type: 'init', state: sharedState }); port.onmessage = function(e) { if (e.data.type === 'updateState') { sharedState = { ...sharedState, ...e.data.state }; // Broadcast state update to all connections connections.forEach(conn => { conn.postMessage({ type: 'stateUpdated', state: sharedState }); }); } }; connections.push(port); port.start(); };

2. Cross-Tab Chat

javascript
// shared-worker.js const connections = []; const messages = []; self.onconnect = function(event) { const port = event.ports[0]; connections.push(port); // Send history messages 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); // Broadcast new message to all connections connections.forEach(conn => { conn.postMessage({ type: 'newMessage', message }); }); }; port.start(); };

3. Shared Counter

javascript
// shared-worker.js let counter = 0; self.onconnect = function(event) { const port = event.ports[0]; // Send current count 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; } // Broadcast updated count connections.forEach(conn => { conn.postMessage({ type: 'counter', value: counter }); }); }; connections.push(port); port.start(); };

Advanced Usage

1. Connection Management

javascript
// shared-worker.js const connections = new Map(); // Use Map to manage connections 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. Broadcast and Directed Messages

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') { // Broadcast to all connections connections.forEach((conn, id) => { conn.postMessage({ type: 'broadcast', from: connectionId, message: e.data.message }); }); } else if (e.data.type === 'sendTo') { // Send to specific connection const targetPort = connections.get(e.data.targetId); if (targetPort) { targetPort.postMessage({ type: 'private', from: connectionId, message: e.data.message }); } } }; port.start(); };

3. Persistent State

javascript
// shared-worker.js let sharedState = {}; // Load initial state from 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(); // Broadcast update connections.forEach(conn => { conn.postMessage({ type: 'stateUpdated', state: sharedState }); }); } }; connections.push(port); port.start(); };

Important Notes

1. Port Must Be Started

javascript
// ❌ Forgot to start port const sharedWorker = new SharedWorker('worker.js'); sharedWorker.port.postMessage('Hello'); // May not send // ✅ Correctly start port const sharedWorker = new SharedWorker('worker.js'); sharedWorker.port.start(); sharedWorker.port.postMessage('Hello');

2. Messages Are Asynchronous

javascript
// Messages won't arrive immediately sharedWorker.port.postMessage('message1'); sharedWorker.port.postMessage('message2'); // Need to receive responses via onmessage

3. Connection Disconnect Handling

javascript
// Worker side port.onclose = function() { console.log('Port closed'); // Clean up resources }; // Main thread side sharedWorker.port.close();

Comparison with Dedicated Worker

FeatureDedicated WorkerShared Worker
ScopeSingle pageMultiple pages shared
Connections1:11:N
CommunicationpostMessageport.postMessage
Use CasesSingle page background tasksCross-page communication

Best Practices

  1. Connection Management: Use Map or array to manage all connections
  2. Error Handling: Add error handling in connection and message processing
  3. State Synchronization: Send current state when new connection is established
  4. Resource Cleanup: Clean up related resources when connection is closed
  5. Message Format: Use unified message format (e.g., { type, data })
标签:Web Worker