Web Worker 详解
一、Web Worker 是什么?
Web Worker 不是把整个 JS 文件放入后台执行,而是创建一个独立的后台线程来执行特定的 JavaScript 代码,避免阻塞主线程(UI 线程)。
二、核心概念
主线程(Main Thread) Worker 线程(独立线程)
│ │
│ postMessage() │
├─────────────────────────────>│
│ │ 执行耗时任务
│ onmessage │
│<─────────────────────────────┤
│ │
三、实战教学
1. 基础用法
主线程文件 (main.js):
// 创建 Worker
const worker = new Worker('worker.js');
// 发送数据给 Worker
worker.postMessage({ type: 'start', data: [1, 2, 3, 4, 5] });
// 接收 Worker 返回的数据
worker.onmessage = function(event) {
console.log('收到 Worker 的消息:', event.data);
};
// 错误处理
worker.onerror = function(error) {
console.error('Worker 错误:', error.message);
};
// 终止 Worker
// worker.terminate();
Worker 文件 (worker.js):
// 监听主线程发来的消息
self.onmessage = function(event) {
console.log('Worker 收到消息:', event.data);
// 执行耗时操作
const result = heavyCalculation(event.data.data);
// 发送结果回主线程
self.postMessage({ result: result });
};
function heavyCalculation(arr) {
// 模拟耗时计算
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += arr[i % arr.length];
}
return sum;
}
2. 实际案例:大数据处理
场景:处理 100 万条数据
// main.js
const worker = new Worker('dataProcessor.js');
// 模拟大量数据
const bigData = Array.from({ length: 1000000 }, (_, i) => ({
id: i,
value: Math.random() * 100
}));
console.time('处理时间');
worker.postMessage({
action: 'filter',
data: bigData,
threshold: 50
});
worker.onmessage = function(e) {
console.timeEnd('处理时间');
console.log('过滤后的数据量:', e.data.length);
// 使用处理后的数据更新 UI
updateUI(e.data);
};
// dataProcessor.js
self.onmessage = function(e) {
const { action, data, threshold } = e.data;
switch(action) {
case 'filter':
const filtered = data.filter(item => item.value > threshold);
self.postMessage(filtered);
break;
case 'sort':
const sorted = data.sort((a, b) => a.value - b.value);
self.postMessage(sorted);
break;
}
};
3. 进阶:带进度反馈
// progressWorker.js
self.onmessage = function(e) {
const data = e.data;
const total = data.length;
const chunkSize = Math.floor(total / 100); // 分成100份
let processed = [];
for (let i = 0; i < total; i++) {
// 处理数据
processed.push(data[i] * 2);
// 每处理一份,报告进度
if (i % chunkSize === 0) {
self.postMessage({
type: 'progress',
percent: Math.floor((i / total) * 100)
});
}
}
// 完成
self.postMessage({
type: 'complete',
data: processed
});
};
// main.js
const worker = new Worker('progressWorker.js');
worker.onmessage = function(e) {
if (e.data.type === 'progress') {
console.log(`进度: ${e.data.percent}%`);
updateProgressBar(e.data.percent);
} else if (e.data.type === 'complete') {
console.log('处理完成!', e.data.data);
}
};
worker.postMessage(largeArray);
四、重要限制
// ❌ Worker 中不能访问的:
// - DOM (document, window)
// - 某些 window 对象的方法
// - parent 对象
// ✅ Worker 中可以使用的:
// - navigator
// - location (只读)
// - XMLHttpRequest / fetch
// - setTimeout / setInterval
// - importScripts() 导入其他脚本
5. 使用 importScripts
// worker.js
importScripts('utils.js', 'math.js');
self.onmessage = function(e) {
// 现在可以使用 utils.js 和 math.js 中的函数
const result = calculateFromUtils(e.data);
self.postMessage(result);
};
五、实用技巧
1. 内联 Worker(无需单独文件)
const workerCode = `
self.onmessage = function(e) {
const result = e.data * 2;
self.postMessage(result);
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const worker = new Worker(workerUrl);
worker.postMessage(10);
worker.onmessage = (e) => console.log(e.data); // 20
2. Worker 池(复用 Worker)
class WorkerPool {
constructor(workerScript, poolSize = 4) {
this.workers = [];
this.taskQueue = [];
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript);
worker.onmessage = (e) => this.handleResult(i, e);
this.workers.push({ worker, busy: false });
}
}
execute(data) {
return new Promise((resolve) => {
const task = { data, resolve };
const availableWorker = this.workers.find(w => !w.busy);
if (availableWorker) {
this.runTask(availableWorker, task);
} else {
this.taskQueue.push(task);
}
});
}
runTask(workerObj, task) {
workerObj.busy = true;
workerObj.currentTask = task;
workerObj.worker.postMessage(task.data);
}
handleResult(index, event) {
const workerObj = this.workers[index];
workerObj.currentTask.resolve(event.data);
workerObj.busy = false;
if (this.taskQueue.length > 0) {
const nextTask = this.taskQueue.shift();
this.runTask(workerObj, nextTask);
}
}
}
// 使用
const pool = new WorkerPool('worker.js', 4);
pool.execute({ num: 100 }).then(result => console.log(result));
六、何时使用 Web Worker?
✅ 适合的场景:
- 大数据计算(排序、过滤、统计)
- 图像/视频处理
- 加密/解密
- 复杂算法运算
- 轮询操作
❌ 不适合的场景:
- 需要频繁与 DOM 交互
- 任务执行时间很短(创建 Worker 有开销)
- 需要访问 DOM API