SharedWorker 与 Worker 的区别


<!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>


posted @ 2025-11-19 18:07  AngDH  阅读(9)  评论(0)    收藏  举报