Worker 基础
Web Worker 是 HTML5 引入的多线程机制,允许在后台线程中运行 JavaScript,避免耗时操作阻塞主线程。Worker 线程无法操作 DOM,但可以进行复杂的计算、网络请求和数据处理,通过 postMessage 与主线程通信。本节介绍 Worker 的创建方式、基本通信机制和核心限制。
前置知识
阅读本节前,建议先了解:持续追踪
基础概念
为什么需要 Web Worker
JavaScript 是单线程语言,耗时操作(大数据计算、图片处理、加密运算)会阻塞主线程,导致页面卡顿或无响应。Web Worker 通过创建独立的后台线程来解决此问题。
| 场景 | 无 Worker | 有 Worker |
|---|---|---|
| 大数据排序 | 页面卡顿 3 秒 | 页面流畅 |
| 图片处理 | UI 无响应 | 后台处理 |
| 复杂计算 | 交互延迟 | 后台计算 |
Worker 的核心特性
| 特性 | 说明 |
|---|---|
| 独立线程 | 与主线程并行执行 |
| 无 DOM 访问 | 无法操作 document、window |
| 通信机制 | 通过 postMessage 传递消息 |
| 同源限制 | Worker 脚本必须与页面同源 |
| 独立作用域 | 有自己的全局对象(DedicatedWorkerGlobalScope) |
语法与创建
创建 Worker
javascript
// 方式一:通过文件路径创建
const worker = new Worker('worker.js');
// 方式二:通过 Blob URL 创建(内联 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 脚本文件 (worker.js)
javascript
// worker.js - Worker 线程的代码
self.onmessage = function(event) {
const data = event.data;
console.log('Worker 收到数据:', data);
// 执行耗时操作
const result = heavyComputation(data);
// 将结果发送回主线程
self.postMessage(result);
};
function heavyComputation(data) {
let sum = 0;
for (let i = 0; i < data; i++) {
sum += Math.sqrt(i);
}
return sum;
}主线程通信
javascript
// 主线程代码
const worker = new Worker('worker.js');
// 接收 Worker 的消息
worker.onmessage = function(event) {
console.log('主线程收到结果:', event.data);
};
// 向 Worker 发送消息
worker.postMessage(1000000);
// 错误处理
worker.onerror = function(error) {
console.error('Worker 错误:', error.message);
};
// 终止 Worker
worker.terminate();详细说明
Worker 的限制
| 限制 | 说明 | 替代方案 |
|---|---|---|
| 不能访问 DOM | 无 document、window 对象 | 通过 postMessage 传递数据 |
| 不能使用 alert/confirm | 不能使用对话框 | 使用 console.log 或 postMessage |
| 同源限制 | 脚本必须与页面同源 | 使用 Blob URL |
| 线程数量有限 | 不宜创建过多 Worker | 使用 Worker 池 |
| 有启动开销 | 创建需要时间 | 复用 Worker |
Worker 可用的 API
| API | 说明 |
|---|---|
self.postMessage() | 发送消息 |
self.onmessage | 接收消息 |
fetch() | 网络请求 |
XMLHttpRequest | 网络请求 |
WebSocket | WebSocket 连接 |
IndexedDB | 本地数据库 |
setTimeout/setInterval | 定时器 |
importScripts() | 加载外部脚本 |
navigator | 部分属性可用 |
localStorage | 可用(部分浏览器) |
console | 控制台输出 |
Worker 生命周期
javascript
// Worker 生命周期事件
// 在 Worker 脚本中
self.onmessage = function(e) {
console.log('Worker 已接收消息');
};
self.onerror = function(e) {
console.error('Worker 内部错误:', e.message);
};
// Worker 关闭
self.close(); // Worker 自行关闭new Worker('worker.js')
│
├── Worker 加载脚本(异步)
│ │
│ └── onmessage 事件就绪
│
├── postMessage() ──── Worker 收到消息并处理
│ │
│ └── self.postMessage() ──── 主线程 onmessage
│
├── worker.terminate() ──── Worker 立即停止
│ 或
└── self.close() ──── Worker 自行关闭importScripts 加载外部库
javascript
// worker.js 中加载外部脚本
importScripts('utils.js', 'math.js');
// 加载后可以使用脚本中的全局变量和函数
const result = calculatePi(1000);
self.postMessage(result);实战示例
大数据计算 Worker
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Web Worker 大数据计算</title>
<style>
body { font-family: -apple-system, sans-serif; padding: 20px; background: #f5f7fa; }
.compute-card {
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
h2 { text-align: center; color: #1a1a2e; }
.input-group {
margin-bottom: 16px;
}
label { display: block; font-weight: 600; margin-bottom: 6px; color: #333; }
input, select {
width: 100%;
padding: 10px 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
color: white;
}
.btn-primary { background: #1a73e8; }
.btn-warning { background: #FF9800; }
.btn-group { display: flex; gap: 10px; margin-top: 16px; }
.result-box {
margin-top: 20px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
min-height: 100px;
}
.result-box h3 { margin-bottom: 12px; color: #333; }
.result-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.result-item:last-child { border-bottom: none; }
.result-label { color: #666; }
.result-value { font-weight: 600; color: #1a73e8; font-family: monospace; }
.progress-bar {
height: 6px;
background: #e0e0e0;
border-radius: 3px;
margin-top: 12px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #4CAF50;
border-radius: 3px;
transition: width 0.3s;
width: 0;
}
.ui-status {
margin-top: 8px;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
display: none;
}
.ui-status.show { display: block; }
.ui-status.blocked { background: #fee2e2; color: #dc2626; }
.ui-status.smooth { background: #d1fae5; color: #065f46; }
</style>
</head>
<body>
<div class="compute-card">
<h2>Web Worker 大数据计算</h2>
<div class="input-group">
<label>计算规模(数字越大越耗时)</label>
<input type="number" id="computeSize" value="50000000" min="1000000" max="500000000">
</div>
<div class="btn-group">
<button class="btn btn-warning" onclick="computeMainThread()">主线程计算(会卡顿)</button>
<button class="btn btn-primary" onclick="computeWithWorker()">Worker 计算(不卡顿)</button>
</div>
<div class="ui-status" id="uiStatus"></div>
<div class="progress-bar" id="progressBar" style="display:none;">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="result-box" id="resultBox">
<h3>计算结果</h3>
<p style="color:#999;">点击按钮开始计算</p>
</div>
</div>
<script>
// 检测 UI 响应能力
let uiBlocked = false;
function checkUIResponsive() {
const statusEl = document.getElementById('uiStatus');
uiBlocked = true;
setTimeout(() => {
if (uiBlocked) {
statusEl.textContent = 'UI 被阻塞了!页面无法响应用户操作。';
statusEl.className = 'ui-status show blocked';
}
}, 100);
uiBlocked = false;
setTimeout(() => {
if (!uiBlocked) {
statusEl.textContent = 'UI 保持流畅!页面正常响应用户操作。';
statusEl.className = 'ui-status show smooth';
}
}, 200);
}
// 主线程计算(会阻塞 UI)
function computeMainThread() {
const size = Number(document.getElementById('computeSize').value);
checkUIResponsive();
const start = performance.now();
let result = 0;
for (let i = 0; i < size; i++) {
result += Math.sqrt(i);
}
const end = performance.now();
showResult('主线程', end - start, result);
}
// Worker 计算(不阻塞 UI)
function computeWithWorker() {
const size = Number(document.getElementById('computeSize').value);
checkUIResponsive();
// 显示进度条
document.getElementById('progressBar').style.display = 'block';
document.getElementById('progressFill').style.width = '0%';
// 创建内联 Worker
const workerCode = `
self.onmessage = function(e) {
const size = e.data;
let result = 0;
for (let i = 0; i < size; i++) {
result += Math.sqrt(i);
// 定期报告进度
if (i % 1000000 === 0) {
self.postMessage({ type: 'progress', value: i / size });
}
}
self.postMessage({ type: 'result', data: result });
};
`;
const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
const start = performance.now();
worker.onmessage = function(e) {
if (e.data.type === 'progress') {
document.getElementById('progressFill').style.width =
(e.data.value * 100).toFixed(0) + '%';
} else if (e.data.type === 'result') {
const elapsed = performance.now() - start;
showResult('Worker', elapsed, e.data.data);
document.getElementById('progressFill').style.width = '100%';
worker.terminate();
}
};
worker.onerror = function(e) {
console.error('Worker 错误:', e.message);
worker.terminate();
};
worker.postMessage(size);
}
function showResult(mode, time, result) {
document.getElementById('resultBox').innerHTML = `
<h3>计算结果</h3>
<div class="result-item">
<span class="result-label">计算模式</span>
<span class="result-value">${mode}</span>
</div>
<div class="result-item">
<span class="result-label">耗时</span>
<span class="result-value">${time.toFixed(2)} ms</span>
</div>
<div class="result-item">
<span class="result-label">结果(前 20 位)</span>
<span class="result-value">${result.toFixed(10)}...</span>
</div>
`;
}
</script>
</body>
</html>注意事项
Worker 创建的最佳实践
- 复用 Worker:避免频繁创建和销毁 Worker
- 控制数量:建议不超过 CPU 核心数(
navigator.hardwareConcurrency) - 错误处理:始终监听
onerror事件 - 及时终止:不使用时调用
worker.terminate()释放资源
javascript
// 获取 CPU 核心数
const cores = navigator.hardwareConcurrency || 4;
console.log(`CPU 核心数: ${cores}`);
// 建议创建 Worker 数不超过 cores - 1最佳实践
- 计算密集型任务优先使用 Worker:排序、搜索、加密、图像处理
- 避免短任务使用 Worker:创建 Worker 有开销,短任务得不偿失
- 使用 Blob URL 实现内联 Worker,减少文件依赖
- 通过消息传递数据,避免共享内存的复杂性
下一节
继续学习:Worker 通信