Web Workers是HTML5提供的API,允许JavaScript在后台线程中运行脚本,避免耗时操作阻塞主线程,从而提升Web应用的性能和用户体验。Web Workers独立于主线程运行,无法直接操作DOM,但可以通过消息机制与主线程通信。
东巴文(db-w.cn) 认为:Web Workers是Web应用多线程编程的基础,让JavaScript也能充分利用多核CPU的计算能力。
| 特点 | 说明 |
|---|---|
| 后台执行 | 在独立线程中运行,不阻塞主线程 |
| 多核利用 | 充分利用多核CPU的计算能力 |
| 异步通信 | 通过消息机制与主线程通信 |
| 独立作用域 | 拥有独立的执行环境和内存空间 |
| 限制 | 说明 |
|---|---|
| 不能操作DOM | 无法访问document、window对象 |
| 不能访问全局变量 | 无法访问主线程的全局变量 |
| 同源限制 | Worker脚本必须与主页面同源 |
| 文件限制 | 不能直接访问本地文件系统 |
专用Worker只能被创建它的脚本访问,一对一通信。
// 主线程
const worker = new Worker('worker.js');
// 发送消息
worker.postMessage({ type: 'start', data: [1, 2, 3, 4, 5] });
// 接收消息
worker.onmessage = function(event) {
console.log('收到Worker消息:', event.data);
};
// 错误处理
worker.onerror = function(error) {
console.error('Worker错误:', error);
};
// worker.js
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'start') {
// 执行耗时计算
const result = data.reduce((sum, num) => sum + num, 0);
// 发送结果回主线程
self.postMessage({ type: 'result', data: result });
}
};
共享Worker可以被多个脚本访问,多对一通信。
// 主线程
const worker = new SharedWorker('shared-worker.js');
// 发送消息
worker.port.postMessage({ type: 'message', data: 'Hello' });
// 接收消息
worker.port.onmessage = function(event) {
console.log('收到共享Worker消息:', event.data);
};
// 启动端口
worker.port.start();
// shared-worker.js
const ports = [];
self.onconnect = function(event) {
const port = event.ports[0];
ports.push(port);
port.onmessage = function(event) {
const { type, data } = event.data;
// 广播消息给所有连接
ports.forEach(p => {
p.postMessage({ type: 'broadcast', data: data });
});
};
port.start();
};
东巴文点评:专用Worker适合单一任务的并行处理,共享Worker适合多页面间的数据共享和通信。
// 主线程
const worker = new Worker('scripts/worker.js');
worker.postMessage({ command: 'calculate', data: 100 });
worker.onmessage = function(event) {
console.log('计算结果:', event.data);
};
worker.onerror = function(error) {
console.error('Worker错误:', error.message);
};
// 创建内联Worker
const workerCode = `
self.onmessage = function(event) {
const data = event.data;
const result = 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 = function(event) {
console.log('结果:', event.data); // 输出: 20
};
// 清理URL
URL.revokeObjectURL(workerUrl);
// 主线程发送消息
worker.postMessage({
type: 'task',
data: {
id: 1,
value: [1, 2, 3, 4, 5]
}
});
// Worker发送消息
self.postMessage({
type: 'result',
data: result
});
// 主线程接收消息
worker.onmessage = function(event) {
const { type, data } = event.data;
switch (type) {
case 'result':
console.log('结果:', data);
break;
case 'progress':
console.log('进度:', data);
break;
}
};
// Worker接收消息
self.onmessage = function(event) {
const { type, data } = event.data;
// 处理消息
};
const channel = new MessageChannel();
// 发送端口给Worker
worker.postMessage({ type: 'init' }, [channel.port2]);
// 主线程监听
channel.port1.onmessage = function(event) {
console.log('主线程收到:', event.data);
};
// Worker中使用
self.onmessage = function(event) {
if (event.data.type === 'init') {
const port = event.ports[0];
port.onmessage = function(e) {
console.log('Worker收到:', e.data);
port.postMessage('Worker回复');
};
}
};
东巴文点评:MessageChannel提供了更灵活的双向通信机制,适合复杂的通信场景。
Worker中使用self对象表示Worker本身。
// worker.js
console.log(self); // DedicatedWorkerGlobalScope
// 监听消息
self.onmessage = function(event) {
// 处理消息
};
// 发送消息
self.postMessage('Hello');
// 关闭Worker
self.close();
// 导入脚本
self.importScripts('utils.js', 'helper.js');
// worker.js
// 导入单个脚本
importScripts('utils.js');
// 导入多个脚本
importScripts('utils.js', 'helper.js', 'constants.js');
// 使用导入的脚本
const result = calculateSum([1, 2, 3, 4, 5]);
self.postMessage(result);
// 主线程
worker.onerror = function(error) {
console.error('Worker错误:', error.message);
console.error('文件:', error.filename);
console.error('行号:', error.lineno);
// 阻止错误继续传播
error.preventDefault();
};
// Worker中
self.onerror = function(error) {
console.error('Worker内部错误:', error);
return true; // 阻止错误传播
};
// 使用try-catch
self.onmessage = function(event) {
try {
// 可能出错的代码
const result = riskyOperation(event.data);
self.postMessage({ success: true, data: result });
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
// 主线程
const worker = new Worker('calculate-worker.js');
worker.postMessage({
type: 'fibonacci',
n: 40
});
worker.onmessage = function(event) {
console.log('斐波那契第40项:', event.data);
};
// calculate-worker.js
self.onmessage = function(event) {
const { type, n } = event.data;
if (type === 'fibonacci') {
const result = fibonacci(n);
self.postMessage(result);
}
};
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 主线程
const worker = new Worker('image-worker.js');
// 发送图像数据
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
worker.postMessage({
type: 'grayscale',
data: imageData
});
worker.onmessage = function(event) {
const processedData = event.data;
ctx.putImageData(processedData, 0, 0);
};
// image-worker.js
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'grayscale') {
const imageData = data;
const pixels = imageData.data;
for (let i = 0; i < pixels.length; i += 4) {
const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
pixels[i] = avg; // R
pixels[i + 1] = avg; // G
pixels[i + 2] = avg; // B
}
self.postMessage(imageData, [imageData.data.buffer]);
}
};
东巴文点评:图像处理是Web Workers的经典应用场景,可以避免处理大量像素数据时阻塞UI。
// 主线程
const worker = new Worker('sort-worker.js');
const largeArray = Array.from({ length: 100000 }, () => Math.random());
worker.postMessage({
type: 'sort',
data: largeArray
});
worker.onmessage = function(event) {
console.log('排序完成,前10个元素:', event.data.slice(0, 10));
};
// sort-worker.js
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'sort') {
const sorted = data.sort((a, b) => a - b);
self.postMessage(sorted);
}
};
// 主线程
const worker = new Worker('timer-worker.js');
worker.postMessage({
type: 'start',
interval: 1000
});
worker.onmessage = function(event) {
console.log('定时器触发:', event.data);
};
// timer-worker.js
let timerId = null;
self.onmessage = function(event) {
const { type, interval } = event.data;
if (type === 'start') {
timerId = setInterval(() => {
self.postMessage(new Date().toISOString());
}, interval);
} else if (type === 'stop') {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
}
};
<!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>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.section {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
button {
padding: 10px 20px;
margin: 5px;
border: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
button:hover {
opacity: 0.9;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.result {
margin: 10px 0;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
font-family: monospace;
}
.progress {
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin: 10px 0;
}
.progress-bar {
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s;
}
canvas {
border: 1px solid #ddd;
margin: 10px 0;
}
input {
padding: 8px;
margin: 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
</style>
</head>
<body>
<h1>Web Workers示例</h1>
<div class="section">
<h2>斐波那契数列计算</h2>
<p>计算斐波那契数列第n项(演示耗时计算)</p>
<input type="number" id="fibInput" value="40" min="1" max="50">
<button onclick="calculateFib()">计算</button>
<button onclick="stopFib()">停止</button>
<div class="result" id="fibResult">等待计算...</div>
</div>
<div class="section">
<h2>大数据排序</h2>
<p>在Worker中排序大量数据</p>
<input type="number" id="sortInput" value="100000" min="1000" max="1000000">
<button onclick="sortData()">排序</button>
<div class="progress">
<div class="progress-bar" id="sortProgress" style="width: 0%"></div>
</div>
<div class="result" id="sortResult">等待排序...</div>
</div>
<div class="section">
<h2>图像处理</h2>
<p>在Worker中处理图像(灰度化)</p>
<canvas id="imageCanvas" width="300" height="200"></canvas>
<br>
<button onclick="processImage()">处理图像</button>
<button onclick="resetImage()">重置</button>
<div class="result" id="imageResult">等待处理...</div>
</div>
<div class="section">
<h2>主线程响应测试</h2>
<p>测试Worker运行时主线程是否响应</p>
<button onclick="testMainThread()">点击测试</button>
<div class="result" id="mainThreadResult">点击次数: 0</div>
</div>
<script>
// Worker管理器
class WorkerManager {
constructor() {
this.workers = {};
}
create(name, workerCode) {
if (this.workers[name]) {
this.workers[name].terminate();
}
const blob = new Blob([workerCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
this.workers[name] = new Worker(url);
URL.revokeObjectURL(url);
return this.workers[name];
}
get(name) {
return this.workers[name];
}
terminate(name) {
if (this.workers[name]) {
this.workers[name].terminate();
delete this.workers[name];
}
}
terminateAll() {
Object.values(this.workers).forEach(worker => worker.terminate());
this.workers = {};
}
}
const workerManager = new WorkerManager();
// 斐波那契Worker代码
const fibWorkerCode = `
self.onmessage = function(event) {
const { type, n } = event.data;
if (type === 'calculate') {
const result = fibonacci(n);
self.postMessage({ type: 'result', data: result });
}
};
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
`;
// 排序Worker代码
const sortWorkerCode = `
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'sort') {
// 发送进度
self.postMessage({ type: 'progress', data: 0 });
// 模拟排序过程
const sorted = [...data].sort((a, b) => a - b);
self.postMessage({ type: 'progress', data: 50 });
// 返回结果
self.postMessage({
type: 'result',
data: {
sorted: sorted.slice(0, 10),
length: sorted.length
}
});
self.postMessage({ type: 'progress', data: 100 });
}
};
`;
// 图像处理Worker代码
const imageWorkerCode = `
self.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'grayscale') {
const pixels = data.data;
for (let i = 0; i < pixels.length; i += 4) {
const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
pixels[i] = avg; // R
pixels[i + 1] = avg; // G
pixels[i + 2] = avg; // B
}
self.postMessage({ type: 'result', data: data }, [data.data.buffer]);
}
};
`;
// 斐波那契计算
function calculateFib() {
const n = parseInt(document.getElementById('fibInput').value);
const resultEl = document.getElementById('fibResult');
resultEl.textContent = '计算中...';
const worker = workerManager.create('fib', fibWorkerCode);
worker.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'result') {
resultEl.textContent = `斐波那契第${n}项: ${data}`;
}
};
worker.onerror = function(error) {
resultEl.textContent = '错误: ' + error.message;
};
worker.postMessage({ type: 'calculate', n: n });
}
function stopFib() {
workerManager.terminate('fib');
document.getElementById('fibResult').textContent = '已停止';
}
// 数据排序
function sortData() {
const length = parseInt(document.getElementById('sortInput').value);
const resultEl = document.getElementById('sortResult');
const progressEl = document.getElementById('sortProgress');
resultEl.textContent = '生成数据中...';
progressEl.style.width = '0%';
// 生成随机数据
const data = Array.from({ length: length }, () => Math.random());
resultEl.textContent = '排序中...';
const worker = workerManager.create('sort', sortWorkerCode);
worker.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'progress') {
progressEl.style.width = data + '%';
} else if (type === 'result') {
resultEl.textContent = `排序完成!数据量: ${data.length}, 前10个: [${data.sorted.map(n => n.toFixed(4)).join(', ')}]`;
}
};
worker.postMessage({ type: 'sort', data: data });
}
// 图像处理
function processImage() {
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
const resultEl = document.getElementById('imageResult');
// 绘制初始图像
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#00ff00');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
resultEl.textContent = '处理中...';
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const worker = workerManager.create('image', imageWorkerCode);
worker.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'result') {
ctx.putImageData(data, 0, 0);
resultEl.textContent = '处理完成!';
}
};
worker.postMessage({ type: 'grayscale', data: imageData }, [imageData.data.buffer]);
}
function resetImage() {
const canvas = document.getElementById('imageCanvas');
const ctx = canvas.getContext('2d');
const resultEl = document.getElementById('imageResult');
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#00ff00');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
resultEl.textContent = '已重置';
}
// 主线程响应测试
let clickCount = 0;
function testMainThread() {
clickCount++;
document.getElementById('mainThreadResult').textContent = `点击次数: ${clickCount}`;
}
// 初始化图像
resetImage();
// 页面卸载时清理Worker
window.addEventListener('beforeunload', function() {
workerManager.terminateAll();
});
</script>
</body>
</html>
// 推荐:耗时任务使用Worker
function processLargeData(data) {
const worker = new Worker('data-processor.js');
worker.postMessage(data);
return new Promise((resolve, reject) => {
worker.onmessage = (e) => resolve(e.data);
worker.onerror = (e) => reject(e.error);
});
}
// 不推荐:简单任务使用Worker(通信开销大于计算开销)
function add(a, b) {
// 简单加法不需要Worker
return a + b;
}
// 推荐:完善的错误处理
const worker = new Worker('worker.js');
worker.onerror = function(error) {
console.error('Worker错误:', {
message: error.message,
filename: error.filename,
lineno: error.lineno,
colno: error.colno
});
// 阻止错误传播
error.preventDefault();
};
worker.onmessageerror = function(error) {
console.error('消息序列化错误:', error);
};
// 不推荐:忽略错误处理
const worker = new Worker('worker.js');
worker.postMessage(data);
// 推荐:及时清理Worker
function processWithWorker(data) {
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
// 处理完成后立即终止
worker.terminate();
console.log('结果:', event.data);
};
worker.onerror = function(error) {
// 出错时也要终止
worker.terminate();
console.error('错误:', error);
};
worker.postMessage(data);
}
// 不推荐:不清理Worker
const worker = new Worker('worker.js');
// 使用后忘记terminate,导致内存泄漏
东巴文点评:Worker是宝贵的资源,使用完毕后应该及时终止,避免内存泄漏。
// 推荐:使用Transferable对象
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 传输所有权,避免复制
worker.postMessage(imageData, [imageData.data.buffer]);
// 不推荐:传输大对象时复制数据
worker.postMessage(imageData); // 会复制整个imageData
问题1:Web Workers中不能直接操作以下哪个对象?
A. self
B. navigator
C. document
D. location
答案:C
东巴文解释:Web Workers运行在独立线程中,无法直接访问DOM,因此不能操作document对象。但可以访问self、navigator、location等对象。
问题2:以下哪个方法用于在Worker中导入外部脚本?
A. import()
B. importScripts()
C. loadScript()
D. require()
答案:B
东巴文解释:在Worker中使用importScripts()方法导入外部脚本,可以导入一个或多个脚本文件。
任务:创建一个Web Worker,实现大数组的过滤功能,过滤出所有大于指定值的元素。
<details> <summary>点击查看参考答案</summary><!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>数组过滤Worker</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
input {
padding: 8px;
margin: 10px 5px;
border: 1px solid #ddd;
border-radius: 3px;
}
button {
padding: 10px 20px;
border: none;
background: #667eea;
color: white;
border-radius: 5px;
cursor: pointer;
}
.result {
margin: 20px 0;
padding: 15px;
background: #f5f5f5;
border-radius: 5px;
}
</style>
</head>
<body>
<h1>数组过滤Worker</h1>
<div>
<label>数组大小:</label>
<input type="number" id="arraySize" value="100000">
</div>
<div>
<label>过滤阈值:</label>
<input type="number" id="threshold" value="0.5" step="0.1">
</div>
<button onclick="filterArray()">开始过滤</button>
<div class="result" id="result">等待过滤...</div>
<script>
// Worker代码
const workerCode = `
self.onmessage = function(event) {
const { type, data, threshold } = event.data;
if (type === 'filter') {
const filtered = data.filter(item => item > threshold);
self.postMessage({
type: 'result',
data: {
originalLength: data.length,
filteredLength: filtered.length,
filtered: filtered.slice(0, 10) // 只返回前10个
}
});
}
};
`;
// 创建Worker
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
const worker = new Worker(workerUrl);
worker.onmessage = function(event) {
const { type, data } = event.data;
if (type === 'result') {
document.getElementById('result').innerHTML = `
<p>原始数组大小: ${data.originalLength}</p>
<p>过滤后大小: ${data.filteredLength}</p>
<p>前10个元素: [${data.filtered.map(n => n.toFixed(4)).join(', ')}]</p>
`;
}
};
function filterArray() {
const size = parseInt(document.getElementById('arraySize').value);
const threshold = parseFloat(document.getElementById('threshold').value);
// 生成随机数组
const array = Array.from({ length: size }, () => Math.random());
document.getElementById('result').textContent = '过滤中...';
// 发送给Worker
worker.postMessage({
type: 'filter',
data: array,
threshold: threshold
});
}
// 清理
window.addEventListener('beforeunload', function() {
worker.terminate();
URL.revokeObjectURL(workerUrl);
});
</script>
</body>
</html>
</details>
东巴文(db-w.cn) - 让编程学习更有趣、更高效!