pin_drop当前位置:知识文库 ❯ 图文
HTML5 API:HTML5 Web Workers - 完整教程与代码示例
一、教程简介
Web Workers 是 HTML5 提供的一种在后台线程中运行 JavaScript 的机制,使得耗时计算不会阻塞主线程(UI 线程),从而保证页面的流畅响应。Worker 线程与主线程之间通过 postMessage 方法进行通信,数据传递采用结构化克隆算法。Web Workers 广泛应用于大数据处理、图像处理、加密计算、复杂算法等场景,是构建高性能 Web 应用的关键技术。
二、核心概念
线程模型
代码示例
主线程(UI 线程) Worker 线程(后台线程)
┌─────────────────┐ ┌─────────────────┐
│ DOM 操作 │ │ 复杂计算 │
│ 事件处理 │ postMessage │ 数据处理 │
│ UI 渲染 │ ←────────────→ │ 网络请求 │
│ 用户交互 │ onmessage │ 文件读写 │
└─────────────────┘ └─────────────────┘Worker 类型
Worker 的限制
Worker 线程中不能执行以下操作:
-
操作 DOM(不能访问
document对象) -
使用
window对象的某些属性和方法 -
使用
localStorage(可使用IndexedDB) -
使用
alert、confirm等弹窗 -
直接访问主线程变量
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 */ };数据传递方式
三、语法与用法
创建 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。支持灰度化、反色、模糊、复古等滤镜效果。
五、浏览器兼容性
六、注意事项与最佳实践
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 中可以使用 fetch 和 XMLHttpRequest:
代码示例
// 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 导致的问题。
本文涉及AI创作
内容由AI创作,请仔细甄别