<!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>
// 生成当前标签页的唯一ID
const 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 URL
function getSharedBlobUrl() {
// 尝试从localStorage获取已存在的Blob URL
const existingBlobUrl = localStorage.getItem('sharedWorkerBlobUrl');
if (existingBlobUrl) {
console.log('使用现有的SharedWorker Blob URL');
blobUrlInfo.textContent = existingBlobUrl.substring(0, 50) + '...';
return existingBlobUrl;
}
// 创建新的Blob URL
console.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 URL
sharedWorkerBlobUrl = 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,强制创建新的SharedWorker
localStorage.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>