pin_drop当前位置:知识文库 ❯ 图文

HTML5 API:HTML5 Web Workers - 完整教程与代码示例

一、教程简介

Web Workers 是 HTML5 提供的一种在后台线程中运行 JavaScript 的机制,使得耗时计算不会阻塞主线程(UI 线程),从而保证页面的流畅响应。Worker 线程与主线程之间通过 postMessage 方法进行通信,数据传递采用结构化克隆算法。Web Workers 广泛应用于大数据处理、图像处理、加密计算、复杂算法等场景,是构建高性能 Web 应用的关键技术。

二、核心概念

线程模型

代码示例

主线程(UI 线程)                    Worker 线程(后台线程)
┌─────────────────┐                ┌─────────────────┐
│  DOM 操作       │                │  复杂计算        │
│  事件处理       │  postMessage   │  数据处理        │
│  UI 渲染       │ ←────────────→ │  网络请求        │
│  用户交互       │  onmessage     │  文件读写        │
└─────────────────┘                └─────────────────┘

Worker 类型

类型 说明 支持
Dedicated Worker 专用 Worker,只能被创建它的页面使用 所有现代浏览器
Shared Worker 共享 Worker,可被同源多个页面共享 Chrome, Firefox, Edge
Service Worker 服务 Worker,用于离线缓存和网络代理 现代浏览器

Worker 的限制

Worker 线程中不能执行以下操作:

  • 操作 DOM(不能访问 document 对象)

  • 使用 window 对象的某些属性和方法

  • 使用 localStorage(可使用 IndexedDB

  • 使用 alertconfirm 等弹窗

  • 直接访问主线程变量

Worker 线程中可以执行以下操作:

  • 使用 XMLHttpRequest / Fetch 发送网络请求

  • 使用 setTimeout / setInterval

  • 使用 IndexedDB

  • 使用 WebSocket

  • 导入其他脚本(importScripts

  • 创建嵌套 Worker

通信机制

代码示例

// 主线程 → Worker
worker.postMessage(data);

// Worker → 主线程
self.postMessage(data);

// 接收消息
worker.onmessage = function(e) { /* e.data */ };
self.onmessage = function(e) { /* e.data */ };

数据传递方式

方式 说明 性能
结构化克隆 默认方式,深拷贝数据 数据量大时较慢
Transferable Objects 转移所有权,零拷贝 高性能,适合大数据
SharedArrayBuffer 共享内存 最高性能,需安全上下文

三、语法与用法

创建 Worker

代码示例

// 方式一:通过文件 URL 创建
const worker = new Worker('worker.js');

// 方式二:通过 Blob URL 创建(内联 Worker)
const code = `
    self.onmessage = function(e) {
        self.postMessage('处理结果: ' + e.data);
    };
`;
const blob = new Blob([code], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));

主线程通信

代码示例

// 发送消息
worker.postMessage({ type: 'calculate', data: [1, 2, 3, 4, 5] });

// 接收消息
worker.onmessage = function(e) {
    console.log('Worker 返回:', e.data);
};

// 错误处理
worker.onerror = function(e) {
    console.error('Worker 错误:', e.message);
};

// 终止 Worker
worker.terminate();

Worker 线程通信

代码示例

// worker.js
self.onmessage = function(e) {
    const result = heavyCalculation(e.data);
    self.postMessage(result);
};

function heavyCalculation(data) {
    // 复杂计算...
    return data.reduce((sum, n) => sum + n, 0);
}

// 导入其他脚本
importScripts('utils.js', 'math.js');

Transferable Objects

代码示例

// 主线程:传递 ArrayBuffer(转移所有权)
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]);
// 传递后,主线程不能再访问 buffer

// Worker 端接收
self.onmessage = function(e) {
    const buffer = e.data; // 直接获得 ArrayBuffer
    // 处理完毕后可以传回
    self.postMessage(buffer, [buffer]);
};

四、代码示例

示例一:大数据计算(斐波那契数列)

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Workers - 大数据计算</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #0a0a23;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }
        .container {
            max-width: 600px;
            width: 100%;
        }
        h1 {
            font-size: 28px;
            color: #fff;
            margin-bottom: 8px;
            text-align: center;
        }
        .desc {
            color: #8b9dc3;
            font-size: 14px;
            margin-bottom: 28px;
            text-align: center;
        }
        .card {
            background: #1b1b32;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            border: 1px solid #2a2a4a;
        }
        .card h2 {
            font-size: 16px;
            color: #fff;
            margin-bottom: 16px;
        }
        .input-row {
            display: flex;
            gap: 12px;
            margin-bottom: 16px;
        }
        input {
            flex: 1;
            padding: 10px 14px;
            border: 1px solid #2a2a4a;
            border-radius: 8px;
            background: #0a0a23;
            color: #fff;
            font-size: 14px;
            outline: none;
        }
        input:focus { border-color: #5b9bd5; }
        button {
            padding: 10px 24px;
            border: none;
            border-radius: 8px;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
        }
        .btn-main {
            background: #5b9bd5;
            color: #fff;
        }
        .btn-main:hover { background: #4a8bc4; }
        .btn-main:disabled { opacity: 0.5; cursor: not-allowed; }
        .btn-compare {
            background: #f0ad4e;
            color: #1b1b32;
        }
        .btn-compare:hover { background: #e09d3e; }
        .btn-compare:disabled { opacity: 0.5; cursor: not-allowed; }
        .result-box {
            background: #0a0a23;
            border-radius: 8px;
            padding: 16px;
            margin-top: 12px;
            min-height: 60px;
        }
        .result-label {
            font-size: 12px;
            color: #8b9dc3;
            margin-bottom: 8px;
        }
        .result-value {
            font-size: 14px;
            color: #5b9bd5;
            font-family: monospace;
            word-break: break-all;
            line-height: 1.6;
        }
        .result-time {
            font-size: 13px;
            color: #f0ad4e;
            margin-top: 8px;
        }
        .progress {
            height: 4px;
            background: #2a2a4a;
            border-radius: 2px;
            margin-top: 12px;
            overflow: hidden;
        }
        .progress-bar {
            height: 100%;
            background: #5b9bd5;
            border-radius: 2px;
            width: 0%;
            transition: width 0.3s;
        }
        .status {
            text-align: center;
            padding: 8px;
            font-size: 13px;
            color: #8b9dc3;
        }
        .status.computing { color: #f0ad4e; }
        .status.done { color: #5cb85c; }
        .status.blocked { color: #d9534f; }
        .compare-section {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 16px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Web Workers 计算演示</h1>
        <p class="desc">对比 Worker 线程与主线程执行耗时计算的差异</p>

        <div class="card">
            <h2>斐波那契数列计算</h2>
            <div class="input-row">
                <input type="number" id="fibN" value="40" min="1" max="50" placeholder="输入 n 值">
                <button class="btn-main" id="workerBtn" onclick="calcWithWorker()">Worker 计算</button>
                <button class="btn-compare" id="mainBtn" onclick="calcOnMain()">主线程计算</button>
            </div>
            <div class="progress"><div class="progress-bar" id="progressBar"></div></div>
            <div class="status" id="status">等待计算...</div>
            <div class="result-box">
                <div class="result-label">计算结果</div>
                <div class="result-value" id="resultValue">--</div>
                <div class="result-time" id="resultTime"></div>
            </div>
        </div>

        <div class="card">
            <h2>性能对比</h2>
            <div class="compare-section">
                <div>
                    <div class="result-label">Worker 线程</div>
                    <div class="result-value" id="workerTime">--</div>
                    <div style="font-size:12px;color:#5cb85c;margin-top:4px;">UI 不阻塞</div>
                </div>
                <div>
                    <div class="result-label">主线程</div>
                    <div class="result-value" id="mainTime">--</div>
                    <div style="font-size:12px;color:#d9534f;margin-top:4px;">UI 会卡顿</div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 内联 Worker 代码
        const workerCode = `
            self.onmessage = function(e) {
                const n = e.data;
                const startTime = performance.now();

                // 计算斐波那契数列
                function fibonacci(num) {
                    if (num <= 1) return num;
                    let a = 0, b = 1;
                    for (let i = 2; i <= num; i++) {
                        const temp = a + b;
                        a = b;
                        b = temp;
                    }
                    return b;
                }

                const result = fibonacci(n);
                const endTime = performance.now();

                self.postMessage({
                    result: result,
                    time: (endTime - startTime).toFixed(2)
                });
            };
        `;

        let worker = null;

        function calcWithWorker() {
            const n = parseInt(document.getElementById('fibN').value);
            if (isNaN(n) || n < 1) { alert('请输入有效数字'); return; }

            document.getElementById('workerBtn').disabled = true;
            document.getElementById('status').className = 'status computing';
            document.getElementById('status').textContent = 'Worker 线程计算中...';
            document.getElementById('progressBar').style.width = '50%';

            // 创建 Worker
            const blob = new Blob([workerCode], { type: 'application/javascript' });
            worker = new Worker(URL.createObjectURL(blob));

            worker.postMessage(n);

            worker.onmessage = function (e) {
                document.getElementById('resultValue').textContent = e.data.result;
                document.getElementById('resultTime').textContent = '耗时: ' + e.data.time + ' ms';
                document.getElementById('workerTime').textContent = e.data.time + ' ms';
                document.getElementById('workerBtn').disabled = false;
                document.getElementById('status').className = 'status done';
                document.getElementById('status').textContent = 'Worker 计算完成';
                document.getElementById('progressBar').style.width = '100%';
                worker.terminate();
            };

            worker.onerror = function (e) {
                document.getElementById('status').className = 'status blocked';
                document.getElementById('status').textContent = 'Worker 错误: ' + e.message;
                document.getElementById('workerBtn').disabled = false;
                worker.terminate();
            };
        }

        function calcOnMain() {
            const n = parseInt(document.getElementById('fibN').value);
            if (isNaN(n) || n < 1) { alert('请输入有效数字'); return; }

            document.getElementById('mainBtn').disabled = true;
            document.getElementById('status').className = 'status blocked';
            document.getElementById('status').textContent = '主线程计算中(UI 会卡顿)...';

            // 使用 setTimeout 让 UI 有机会更新
            setTimeout(function () {
                const startTime = performance.now();

                function fibonacci(num) {
                    if (num <= 1) return num;
                    let a = 0, b = 1;
                    for (let i = 2; i <= num; i++) {
                        const temp = a + b;
                        a = b;
                        b = temp;
                    }
                    return b;
                }

                const result = fibonacci(n);
                const endTime = performance.now();
                const time = (endTime - startTime).toFixed(2);

                document.getElementById('resultValue').textContent = result;
                document.getElementById('resultTime').textContent = '耗时: ' + time + ' ms';
                document.getElementById('mainTime').textContent = time + ' ms';
                document.getElementById('mainBtn').disabled = false;
                document.getElementById('status').className = 'status done';
                document.getElementById('status').textContent = '主线程计算完成';
            }, 50);
        }
    </script>
</body>
</html>

示例二:图像处理 Worker

使用 Web Worker 在后台线程中处理图像像素数据,不阻塞 UI。支持灰度化、反色、模糊、复古等滤镜效果。

五、浏览器兼容性

浏览器 Dedicated Worker Shared Worker Service Worker
Chrome 4+ 4+ 40+
Firefox 3.5+ 29+ 44+
Safari 4+ 16+ 11.1+
Edge 12+ 79+ 17+
Opera 10.6+ 10.6+ 27+
IE 10+ 不支持 不支持

六、注意事项与最佳实践

1. Worker 创建与销毁

代码示例

// 推荐:复用 Worker 而非频繁创建销毁
let worker = null;

function getWorker() {
    if (!worker) {
        worker = new Worker('worker.js');
    }
    return worker;
}

// 页面卸载时终止
window.addEventListener('beforeunload', function() {
    if (worker) worker.terminate();
});

2. 数据传递优化

代码示例

// 大数据使用 Transferable Objects
const largeArray = new Float64Array(1000000);
worker.postMessage(largeArray.buffer, [largeArray.buffer]);

// 避免频繁传递大数据
// 不推荐:每帧都传
// 推荐:批量传递,Worker 内部分帧处理

3. 错误处理

代码示例

const worker = new Worker('worker.js');

worker.onerror = function(e) {
    console.error('Worker 错误:', {
        message: e.message,
        filename: e.filename,
        lineno: e.lineno,
        colno: e.colno
    });
    worker.terminate();
};

4. 内联 Worker 的最佳写法

代码示例

// 使用 Blob URL 创建内联 Worker
function createInlineWorker(fn) {
    const code = '(' + fn.toString() + ')()';
    const blob = new Blob([code], { type: 'application/javascript' });
    return new Worker(URL.createObjectURL(blob));
}

// 使用
const worker = createInlineWorker(function() {
    self.onmessage = function(e) {
        // Worker 逻辑
        self.postMessage('result');
    };
});

七、代码规范示例

代码示例

// 推荐:封装 Worker 管理类
class WorkerPool {
    constructor(workerUrl, poolSize = 4) {
        this.workerUrl = workerUrl;
        this.poolSize = poolSize;
        this.workers = [];
        this.taskQueue = [];
        this.init();
    }

    init() {
        for (let i = 0; i < this.poolSize; i++) {
            const worker = new Worker(this.workerUrl);
            worker.busy = false;
            worker.onmessage = (e) => {
                if (worker.resolve) {
                    worker.resolve(e.data);
                    worker.resolve = null;
                }
                worker.busy = false;
                this.processQueue();
            };
            worker.onerror = (e) => {
                if (worker.reject) {
                    worker.reject(new Error(e.message));
                    worker.reject = null;
                }
                worker.busy = false;
                this.processQueue();
            };
            this.workers.push(worker);
        }
    }

    execute(data) {
        return new Promise((resolve, reject) => {
            const task = { data, resolve, reject };
            this.taskQueue.push(task);
            this.processQueue();
        });
    }

    processQueue() {
        if (this.taskQueue.length === 0) return;

        const availableWorker = this.workers.find(w => !w.busy);
        if (!availableWorker) return;

        const task = this.taskQueue.shift();
        availableWorker.busy = true;
        availableWorker.resolve = task.resolve;
        availableWorker.reject = task.reject;
        availableWorker.postMessage(task.data);
    }

    terminate() {
        this.workers.forEach(w => w.terminate());
        this.workers = [];
    }
}

// 使用示例
const pool = new WorkerPool('worker.js', 4);

async function processTasks() {
    const tasks = [1, 2, 3, 4, 5, 6, 7, 8];
    const results = await Promise.all(
        tasks.map(n => pool.execute({ num: n }))
    );
    console.log('所有任务完成:', results);
    pool.terminate();
}

八、常见问题与解决方案

Q1:Worker 中可以使用 ES Module 吗?

解答:可以。使用 { type: 'module' } 选项创建 Worker:

代码示例

const worker = new Worker('worker.js', { type: 'module' });

Worker 文件中可以使用 import/export 语法。

Q2:Worker 中如何发起网络请求?

解答:Worker 中可以使用 fetchXMLHttpRequest

代码示例

// worker.js
self.onmessage = async function(e) {
    const response = await fetch(e.data.url);
    const data = await response.json();
    self.postMessage(data);
};

Q3:如何调试 Worker?

解答:在 Chrome DevTools 中,Sources 面板可以看到 Worker 线程,可以设置断点调试。也可以在 Worker 中使用 console.log,输出会显示在主线程的控制台中。

Q4:Worker 文件跨域怎么办?

解答:Worker 文件必须同源。如果需要加载跨域脚本,可以使用 importScripts,或通过 Blob URL 创建内联 Worker。

九、总结

Web Workers 为 JavaScript 提供了真正的多线程能力,通过将耗时计算移到后台线程,确保主线程的 UI 响应不受影响。核心通信机制是 postMessage/onmessage,大数据传递可使用 Transferable Objects 实现零拷贝。使用时需注意 Worker 中不能操作 DOM、数据传递采用深拷贝、需要合理管理 Worker 的创建与销毁。通过 Worker Pool 等模式可以高效复用 Worker 线程,提升整体性能。


常见问题

Worker 中可以使用 ES Module 吗?

可以。使用 { type: 'module' } 选项创建 Worker,Worker 文件中可以使用 import/export 语法。

Worker 中如何发起网络请求?

Worker 中可以使用 fetch 和 XMLHttpRequest 发送网络请求,与主线程中的用法相同。

如何调试 Worker?

在 Chrome DevTools 的 Sources 面板可以看到 Worker 线程并设置断点调试。也可以在 Worker 中使用 console.log,输出会显示在主线程的控制台中。

Worker 文件跨域怎么办?

Worker 文件必须同源。如果需要加载跨域脚本,可以使用 importScripts,或通过 Blob URL 创建内联 Worker。

Worker 中可以操作 DOM 吗?

不可以。Worker 线程中不能访问 document 对象,不能操作 DOM。这是为了确保线程安全,避免并发访问 DOM 导致的问题。

标签: Web Workers 后台线程 postMessage 多线程 图像处理 Worker Pool

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:HTML5 LocalStorage - 完整教程与代码示例 下一篇: HTML5 API:HTML5 Server-Sent Events - 完整教程与代码示例

poll相关推荐