SharedWorker
一、SharedWorker 概述
1.1 什么是 SharedWorker
SharedWorker 是一种特殊的 Web Worker,可以被同源的不同窗口、标签页、iframe 同时使用。与普通 Web Worker(专用)不同,SharedWorker 可以被多个浏览上下文共享。
// 创建 SharedWorker
const worker = new SharedWorker('/js/shared-worker.js');
// 通过端口通信
worker.port.addEventListener('message', (e) => {
console.log('Received:', e.data);
});
worker.port.start();
worker.port.postMessage('Hello from main thread');
1.2 与 Web Worker 的区别
| 特性 | Web Worker | SharedWorker |
|---|---|---|
| 作用域 | 仅创建它的脚本 | 同源所有脚本共享 |
| 多实例 | 每次 new 创建新实例 | 同脚本同一实例 |
| 通信方式 | 直接 postMessage | 通过 port.postMessage |
| 状态共享 | 独立状态 | 可共享状态 |
| 标签页通信 | 需要通过主线程中转 | 可直接互相通信 |
二、SharedWorker 实现
2.1 Worker 脚本
// shared-worker.js
const connections = new Set();
let sharedState = { count: 0 };
self.onconnect = (e) => {
// 为每个连接创建端口
const port = e.ports[0];
// 添加到连接集合
connections.add(port);
// 发送当前状态
port.postMessage({ type: 'init', state: sharedState });
// 监听消息
port.onmessage = (event) => {
const { type, data } = event.data;
if (type === 'increment') {
sharedState.count += 1;
// 广播到所有连接
connections.forEach(p => {
p.postMessage({
type: 'update',
state: sharedState
});
});
}
};
// 启动端口
port.start();
// 连接关闭时移除
port.onmessage = null;
port.onmessageerror = null;
connections.delete(port);
};
2.2 客户端代码
// main.js
const worker = new SharedWorker('/js/shared-worker.js');
worker.port.addEventListener('message', (e) => {
const { type, state } = e.data;
if (type === 'init') {
console.log('Initial state:', state);
} else if (type === 'update') {
console.log('Updated state:', state);
updateUI(state);
}
});
worker.port.start();
// 发送消息
document.getElementById('btn').addEventListener('click', () => {
worker.port.postMessage({ type: 'increment' });
});
三、端口通信
3.1 端口基础
// 创建时自动获得 port
const sharedWorker = new SharedWorker('worker.js');
// 显式获取端口
const port = sharedWorker.port;
// 启动端口
port.start();
// 发送消息
port.postMessage({ type: 'ping' });
// 接收消息
port.onmessage = (e) => {
console.log('Message:', e.data);
};
3.2 多个标签页通信
// SharedWorker 内部
self.onconnect = (e) => {
const port = e.ports[0];
port.onmessage = (event) => {
// 接收来自任意标签页的消息
const { from, message } = event.data;
// 广播给所有其他标签页
connections.forEach(p => {
if (p !== port) {
p.postMessage({
from: 'another-tab',
message: message
});
}
});
};
port.start();
};
3.3 错误处理
port.onmessageerror = (e) => {
console.error('Message error:', e);
};
// SharedWorker 内部错误
self.onerror = (e) => {
console.error('Worker error:', e.message, e.filename, e.lineno);
};
四、实际应用
4.1 跨标签页状态同步
// SharedWorker: state-manager.js
const state = {
user: null,
settings: {},
cache: {}
};
const subscribers = new Set();
self.onconnect = (e) => {
const port = e.ports[0];
subscribers.add(port);
// 发送初始状态
port.postMessage({ type: 'init', state });
port.onmessage = (event) => {
const { type, payload } = event.data;
switch (type) {
case 'update':
Object.assign(state, payload);
// 广播更新
subscribers.forEach(p => {
p.postMessage({ type: 'update', state });
});
break;
case 'subscribe':
// 发送完整状态
port.postMessage({ type: 'init', state });
break;
}
};
port.start();
};
4.2 连接池管理
// shared-worker.js - 连接池
const connections = [];
const MAX_CONNECTIONS = 10;
self.onconnect = (e) => {
if (connections.length >= MAX_CONNECTIONS) {
e.ports[0].postMessage({
type: 'error',
message: 'Connection pool full'
});
return;
}
const port = e.ports[0];
connections.push(port);
// ... 处理连接 ...
port.onmessage = null;
connections.splice(connections.indexOf(port), 1);
};
4.3 心跳检测
// shared-worker.js
const heartbeatInterval = 5000;
let lastHeartbeat = Date.now();
setInterval(() => {
if (Date.now() - lastHeartbeat > heartbeatInterval * 2) {
// 标签页可能已崩溃,清理僵尸连接
cleanupZombieConnections();
}
}, heartbeatInterval);
function cleanupZombieConnections() {
connections.forEach(port => {
port.postMessage({ type: 'ping' });
});
}
五、注意事项
5.1 浏览器支持
// 检测支持
if ('SharedWorker' in window) {
console.log('SharedWorker supported');
} else {
console.warn('SharedWorker not supported');
}
5.2 HTTPS 要求
// SharedWorker 需要安全上下文
if (window.isSecureContext) {
// SharedWorker 可用
} else {
console.warn('SharedWorker requires HTTPS');
}
5.3 调试
// Chrome DevTools 中查看 SharedWorker
// 1. 打开 DevTools
// 2. 选择 "Sources" 面板
// 3. 在 "Threads" 或 "Workers" 中查看
// 4. 设置断点进行调试
六、面试高频问题
Q: SharedWorker 和 Web Worker 有什么区别?
回答要点:Web Worker 是专用的,只能被创建它的脚本使用;SharedWorker 可以被同源的多个脚本共享。这使得 SharedWorker 适合需要跨标签页共享状态或通信的场景,而 Web Worker 适合隔离的独立任务。
Q: SharedWorker 的端口通信机制是什么?
回答要点:SharedWorker 通过端口(port)与每个连接建立通信。onconnect 事件提供端口,通过 port.postMessage 发送消息,port.onmessage 接收消息。多个标签页通过各自的端口与 SharedWorker 通信,SharedWorker 可以广播消息给所有或部分端口。
Q: SharedWorker 适合什么应用场景?
回答要点:跨标签页的状态同步(如用户登录状态);跨标签页的数据库连接池;跨标签页的缓存管理;跨标签页的日志收集;需要长连接且需要在多标签页间共享的场景(如 WebSocket 连接池)。