<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SharedWorker 与 Worker 的区别 - 单文件实现</title><style>* {box-sizing: border-box;margin: 0;padding: 0;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;}body {background-color: #f5f7fa;color: #333;line-height: 1.6;padding: 20px;}.container {max-width: 1200px;margin: 0 auto;}header {text-align: center;margin-bottom: 40px;padding: 20px;background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);color: white;border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);}h1 {font-size: 2.5rem;margin-bottom: 10px;}.subtitle {font-size: 1.2rem;opacity: 0.9;}.comparison {display: flex;flex-wrap: wrap;gap: 30px;margin-bottom: 40px;}.card {flex: 1;min-width: 300px;background: white;border-radius: 10px;padding: 25px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);transition: transform 0.3s ease;}.card:hover {transform: translateY(-5px);}.worker-card {border-top: 5px solid #ff6b6b;}.shared-worker-card {border-top: 5px solid #4cd97b;}.card h2 {display: flex;align-items: center;margin-bottom: 20px;font-size: 1.8rem;}.worker-card h2:before {content: "🔒";margin-right: 10px;}.shared-worker-card h2:before {content: "🔗";margin-right: 10px;}.feature-list {list-style-type: none;margin: 20px 0;}.feature-list li {padding: 10px 0;border-bottom: 1px solid #eee;display: flex;align-items: center;}.feature-list li:before {content: "✓";margin-right: 10px;font-weight: bold;}.worker-card .feature-list li:before {color: #ff6b6b;}.shared-worker-card .feature-list li:before {color: #4cd97b;}.demo-section {display: flex;flex-wrap: wrap;gap: 30px;margin-bottom: 40px;}.demo-panel {flex: 1;min-width: 300px;background: white;border-radius: 10px;padding: 25px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);}.demo-panel h3 {margin-bottom: 20px;font-size: 1.5rem;color: #444;}.demo-controls {display: flex;gap: 10px;margin-bottom: 20px;flex-wrap: wrap;}button {padding: 10px 15px;border: none;border-radius: 5px;cursor: pointer;font-weight: bold;transition: all 0.3s ease;}.worker-btn {background-color: #ff6b6b;color: white;}.worker-btn:hover {background-color: #ff5252;}.shared-worker-btn {background-color: #4cd97b;color: white;}.shared-worker-btn:hover {background-color: #38cc6c;}.output {background-color: #f8f9fa;border: 1px solid #e9ecef;border-radius: 5px;padding: 15px;min-height: 150px;max-height: 300px;overflow-y: auto;font-family: 'Courier New', monospace;font-size: 0.9rem;white-space: pre-wrap;}.status {margin-top: 10px;padding: 10px;border-radius: 5px;font-weight: bold;}.status.connected {background-color: #e8f5e9;color: #2e7d32;}.status.disconnected {background-color: #ffebee;color: #c62828;}.tab-info {background-color: #e3f2fd;border-left: 4px solid #2196f3;padding: 10px 15px;margin: 10px 0;border-radius: 0 5px 5px 0;}.technique-info {background-color: #fff3cd;border: 1px solid #ffeaa7;border-radius: 5px;padding: 15px;margin: 20px 0;}.code-example {background-color: #2d2d2d;color: #f8f8f2;border-radius: 5px;padding: 20px;margin-top: 30px;overflow-x: auto;}.code-example h3 {color: #f8f8f2;margin-bottom: 15px;}pre {white-space: pre-wrap;line-height: 1.5;}.highlight {color: #ff79c6;}.comment {color: #6272a4;}footer {text-align: center;padding: 20px;color: #666;font-size: 0.9rem;}@media (max-width: 768px) {.comparison, .demo-section {flex-direction: column;}.card, .demo-panel {min-width: 100%;}}</style>
</head>
<body><div class="container"><header><h1>SharedWorker 与 Worker 的区别 - 单文件实现</h1><p class="subtitle">使用Blob URL实现单文件SharedWorker演示</p></header><div class="technique-info"><h3>💡 技术实现说明</h3><p>此演示使用了一种技巧来实现单文件SharedWorker:</p><ul><li>使用<code>localStorage</code>在所有标签页间共享同一个Blob URL</li><li>第一个标签页创建Blob URL并保存到localStorage</li><li>后续标签页从localStorage读取并使用相同的Blob URL</li><li>这样所有标签页都会连接到同一个SharedWorker实例</li></ul></div><div class="tab-info"><strong>当前标签页ID: <span id="currentTabId">生成中...</span></strong><br><span>SharedWorker Blob URL: <span id="blobUrlInfo">未生成</span></span></div><div class="comparison"><div class="card worker-card"><h2>专用Worker (Dedicated Worker)</h2><p>专用Worker与创建它的脚本一对一关联,只能被创建它的页面访问。</p><ul class="feature-list"><li>与创建它的脚本一对一关联</li><li>生命周期与创建页面绑定</li><li>无法被其他页面或Worker访问</li><li>所有主流浏览器都支持</li><li>适用于单页面内的复杂计算任务</li></ul></div><div class="card shared-worker-card"><h2>共享Worker (SharedWorker)</h2><p>共享Worker可以被多个脚本共享,只要这些脚本与共享Worker同源。</p><ul class="feature-list"><li>可以被多个浏览器上下文(窗口、iframe等)共享</li><li>生命周期独立于创建它的页面</li><li>通过端口(port)与不同页面通信</li><li>浏览器支持相对较少</li><li>适用于多标签页应用的数据同步</li></ul></div></div><div class="demo-section"><div class="demo-panel"><h3>Worker 演示</h3><div class="demo-controls"><button class="worker-btn" id="startWorker">启动Worker</button><button class="worker-btn" id="sendToWorker">发送消息</button><button class="worker-btn" id="terminateWorker">终止Worker</button></div><div class="output" id="workerOutput">Worker输出将显示在这里...</div><div id="workerStatus" class="status disconnected">Worker状态: 未连接</div></div><div class="demo-panel"><h3>SharedWorker 演示(单文件实现)</h3><div class="demo-controls"><button class="shared-worker-btn" id="startSharedWorker">连接SharedWorker</button><button class="shared-worker-btn" id="sendToSharedWorker">发送消息</button><button class="shared-worker-btn" id="terminateSharedWorker">断开连接</button><button class="shared-worker-btn" id="newTab">在新标签页中打开</button><button class="shared-worker-btn" id="resetSharedWorker">重置SharedWorker</button></div><div class="output" id="sharedWorkerOutput">SharedWorker输出将显示在这里...</div><div id="sharedWorkerStatus" class="status disconnected">SharedWorker状态: 未连接 | 当前连接数: 0</div><p style="margin-top: 10px; font-size: 0.9rem; color: #666;">提示:打开多个标签页测试真正的SharedWorker共享功能</p></div></div><div class="code-example"><h3>单文件SharedWorker实现代码</h3><pre><code><span class="comment">// SharedWorker脚本内容(嵌入在HTML中)</span>
const sharedWorkerScript = `let connectionCount = 0;let ports = [];let messageCount = 0;self.onconnect = function(event) {const port = event.ports[0];connectionCount++;const clientId = 'client_' + Math.random().toString(36).substr(2, 9);port.clientId = clientId;ports.push(port);<span class="comment">// 欢迎新客户端</span>port.postMessage({type: 'welcome',clientId: clientId,connectionCount: connectionCount,message: '欢迎!你是第' + connectionCount + '个客户端'});<span class="comment">// 通知其他客户端</span>broadcastMessage({type: 'connection_update',connectionCount: connectionCount,message: '新客户端连接,当前共有' + connectionCount + '个连接'}, port);port.onmessage = function(event) {messageCount++;const message = event.data;<span class="comment">// 广播消息给所有客户端</span>broadcastMessage({type: 'client_message',clientId: clientId,messageId: messageCount,content: message,message: '客户端[' + clientId + '] 说: ' + message});};port.onclose = function() {const index = ports.indexOf(port);if (index > -1) {ports.splice(index, 1);connectionCount--;broadcastMessage({type: 'connection_update',connectionCount: connectionCount,message: '客户端断开,剩余' + connectionCount + '个连接'});}};port.start();};function broadcastMessage(message, excludePort = null) {ports.forEach(port => {if (port !== excludePort) {try {port.postMessage(message);} catch (e) {console.log('发送消息失败');}}});}
`;<span class="comment">// 创建Blob URL并共享给所有标签页</span>
const blob = new Blob([sharedWorkerScript], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);<span class="comment">// 保存到localStorage供其他标签页使用</span>
localStorage.setItem('sharedWorkerBlobUrl', blobUrl);<span class="comment">// 创建SharedWorker</span>
const sharedWorker = new SharedWorker(blobUrl, 'demo-shared-worker');</code></pre></div><footer><p>© 2023 Web Workers 单文件演示 | 使用Blob URL技巧实现SharedWorker共享</p></footer></div><script>// 生成当前标签页的唯一IDconst tabId = 'tab_' + Math.random().toString(36).substr(2, 9);document.getElementById('currentTabId').textContent = tabId;// SharedWorker脚本内容const sharedWorkerScriptContent = `
let connectionCount = 0;
let ports = [];
let messageCount = 0;self.onconnect = function(event) {const port = event.ports[0];connectionCount++;const clientId = 'client_' + Math.random().toString(36).substr(2, 9);port.clientId = clientId;ports.push(port);console.log('新的SharedWorker连接,客户端ID:', clientId, '总连接数:', connectionCount);// 向新客户端发送欢迎消息port.postMessage({type: 'welcome',clientId: clientId,connectionCount: connectionCount,message: '欢迎连接到SharedWorker!你是第' + connectionCount + '个客户端'});// 向其他客户端广播连接更新broadcastMessage({type: 'connection_update',connectionCount: connectionCount,message: '系统: 新客户端连接,当前共有' + connectionCount + '个连接'}, port);// 处理客户端消息port.onmessage = function(event) {messageCount++;const message = event.data;console.log('收到来自客户端', clientId, '的消息:', message);// 广播消息给所有客户端broadcastMessage({type: 'client_message',clientId: clientId,messageId: messageCount,content: message,timestamp: new Date().toISOString(),message: '客户端[' + clientId + '] 说: ' + message});};// 处理连接关闭port.onclose = function() {const index = ports.indexOf(port);if (index > -1) {ports.splice(index, 1);connectionCount--;console.log('SharedWorker连接关闭,客户端ID:', clientId, '剩余连接数:', connectionCount);// 通知其他客户端broadcastMessage({type: 'connection_update',connectionCount: connectionCount,message: '系统: 客户端断开连接,剩余' + connectionCount + '个连接'});}};port.start();
};function broadcastMessage(message, excludePort = null) {const validPorts = ports.filter(port => port && port !== excludePort);validPorts.forEach(port => {try {port.postMessage(message);} catch (e) {console.log('向客户端发送消息失败,可能已断开连接');}});// 清理无效的端口引用ports = validPorts;
}`;// Worker 演示代码let worker;const workerOutput = document.getElementById('workerOutput');const workerStatus = document.getElementById('workerStatus');document.getElementById('startWorker').addEventListener('click', function() {if (worker) {workerOutput.textContent += '\nWorker已经存在,先终止旧Worker';worker.terminate();}const workerScript = `let messageCount = 0;self.onmessage = function(e) {messageCount++;const message = e.data;self.postMessage('Worker收到第' + messageCount + '条消息: ' + message + ' | 时间: ' + new Date().toLocaleTimeString());let result = 0;for (let i = 0; i < 10000000; i++) {result += Math.sqrt(i);}self.postMessage('计算完成! 结果: ' + result.toString().substring(0, 10) + '...');};`;const blob = new Blob([workerScript], { type: 'application/javascript' });const blobUrl = URL.createObjectURL(blob);worker = new Worker(blobUrl);worker.onmessage = function(e) {workerOutput.textContent += '\n' + e.data;workerOutput.scrollTop = workerOutput.scrollHeight;};workerOutput.textContent = 'Worker已启动! (标签页: ' + tabId + ')';workerStatus.textContent = 'Worker状态: 已连接';workerStatus.className = 'status connected';});document.getElementById('sendToWorker').addEventListener('click', function() {if (worker) {worker.postMessage('来自标签页 ' + tabId + ' 的消息 #' + Math.floor(Math.random() * 100));} else {workerOutput.textContent += '\n请先启动Worker!';}});document.getElementById('terminateWorker').addEventListener('click', function() {if (worker) {worker.terminate();worker = null;workerOutput.textContent += '\nWorker已终止!';workerStatus.textContent = 'Worker状态: 未连接';workerStatus.className = 'status disconnected';}});// SharedWorker 单文件实现let sharedWorker;let sharedWorkerBlobUrl;const sharedWorkerOutput = document.getElementById('sharedWorkerOutput');const sharedWorkerStatus = document.getElementById('sharedWorkerStatus');const blobUrlInfo = document.getElementById('blobUrlInfo');let sharedWorkerClientId = null;// 获取或创建共享的Blob URLfunction getSharedBlobUrl() {// 尝试从localStorage获取已存在的Blob URLconst existingBlobUrl = localStorage.getItem('sharedWorkerBlobUrl');if (existingBlobUrl) {console.log('使用现有的SharedWorker Blob URL');blobUrlInfo.textContent = existingBlobUrl.substring(0, 50) + '...';return existingBlobUrl;}// 创建新的Blob URLconsole.log('创建新的SharedWorker Blob URL');const blob = new Blob([sharedWorkerScriptContent], { type: 'application/javascript' });const newBlobUrl = URL.createObjectURL(blob);// 保存到localStorage供其他标签页使用localStorage.setItem('sharedWorkerBlobUrl', newBlobUrl);blobUrlInfo.textContent = newBlobUrl.substring(0, 50) + '...';return newBlobUrl;}document.getElementById('startSharedWorker').addEventListener('click', function() {if (sharedWorker) {sharedWorkerOutput.textContent += '\nSharedWorker已经连接';return;}try {// 获取共享的Blob URLsharedWorkerBlobUrl = getSharedBlobUrl();// 创建SharedWorker(使用相同的名称确保共享)sharedWorker = new SharedWorker(sharedWorkerBlobUrl, 'demo-shared-worker');sharedWorker.port.onmessage = function(event) {const data = event.data;switch (data.type) {case 'welcome':sharedWorkerClientId = data.clientId;sharedWorkerOutput.textContent = 'SharedWorker连接成功!\\n';sharedWorkerOutput.textContent += '客户端ID: ' + data.clientId + '\\n';sharedWorkerOutput.textContent += data.message;updateSharedWorkerStatus(true, data.connectionCount);break;case 'connection_update':sharedWorkerOutput.textContent += '\\n🔗 ' + data.message;updateSharedWorkerStatus(true, data.connectionCount);break;case 'client_message':if (data.clientId !== sharedWorkerClientId) {sharedWorkerOutput.textContent += '\\n📨 来自[' + data.clientId + ']: ' + data.content + ' | 时间: ' + new Date().toLocaleTimeString();}break;}sharedWorkerOutput.scrollTop = sharedWorkerOutput.scrollHeight;};sharedWorker.port.start();} catch (error) {sharedWorkerOutput.textContent = 'SharedWorker连接失败: ' + error.message;console.error('SharedWorker错误:', error);}});document.getElementById('sendToSharedWorker').addEventListener('click', function() {if (sharedWorker && sharedWorkerClientId) {const message = '来自标签页 ' + tabId + ' 的消息 #' + Math.floor(Math.random() * 100);sharedWorker.port.postMessage(message);sharedWorkerOutput.textContent += '\\n📤 我发送: ' + message;sharedWorkerOutput.scrollTop = sharedWorkerOutput.scrollHeight;} else {sharedWorkerOutput.textContent += '\\n请先连接SharedWorker!';}});document.getElementById('terminateSharedWorker').addEventListener('click', function() {if (sharedWorker) {sharedWorker.port.close();sharedWorker = null;sharedWorkerClientId = null;sharedWorkerOutput.textContent += '\\nSharedWorker连接已关闭!';updateSharedWorkerStatus(false, 0);}});document.getElementById('newTab').addEventListener('click', function() {window.open(window.location.href, '_blank');});document.getElementById('resetSharedWorker').addEventListener('click', function() {// 清理localStorage中的Blob URL,强制创建新的SharedWorkerlocalStorage.removeItem('sharedWorkerBlobUrl');if (sharedWorkerBlobUrl) {URL.revokeObjectURL(sharedWorkerBlobUrl);sharedWorkerBlobUrl = null;}if (sharedWorker) {sharedWorker.port.close();sharedWorker = null;}sharedWorkerClientId = null;sharedWorkerOutput.textContent = 'SharedWorker已重置,请重新连接';updateSharedWorkerStatus(false, 0);blobUrlInfo.textContent = '未生成';});function updateSharedWorkerStatus(connected, count) {if (connected) {sharedWorkerStatus.textContent = 'SharedWorker状态: 已连接 | 当前连接数: ' + count;sharedWorkerStatus.className = 'status connected';} else {sharedWorkerStatus.textContent = 'SharedWorker状态: 未连接 | 当前连接数: 0';sharedWorkerStatus.className = 'status disconnected';}}// 页面卸载时清理资源window.addEventListener('beforeunload', function() {if (worker) {worker.terminate();}if (sharedWorker) {sharedWorker.port.close();}});// 初始化:显示当前的Blob URL状态const existingBlobUrl = localStorage.getItem('sharedWorkerBlobUrl');if (existingBlobUrl) {blobUrlInfo.textContent = existingBlobUrl.substring(0, 50) + '...';}</script>
</body>
</html>