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
portobject for communication - Handles new connections through
onconnectevent - 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
| Feature | Dedicated Worker | Shared Worker |
|---|---|---|
| Scope | Single page | Multiple pages shared |
| Connections | 1:1 | 1:N |
| Communication | postMessage | port.postMessage |
| Use Cases | Single page background tasks | Cross-page communication |
Best Practices
- Connection Management: Use Map or array to manage all connections
- Error Handling: Add error handling in connection and message processing
- State Synchronization: Send current state when new connection is established
- Resource Cleanup: Clean up related resources when connection is closed
- Message Format: Use unified message format (e.g.,
{ type, data })