每天一个高级前端知识 - Day 19
今日主题:Web Worker 深度应用 - 多线程前端计算与高性能数据处理
核心概念:JavaScript 的单线程不是限制,而是需要被管理的资源
Web Worker 让浏览器拥有了真正的并行计算能力,主线程负责 UI,Worker 负责计算。
🧵 Worker 类型与架构
┌─────────────────────────────────────────────────────┐
│ 主线程 (UI Thread) │
│ • DOM 操作 • 事件响应 • 渲染 • 用户交互 │
└─────────────────────────────────────────────────────┘
↕ postMessage / onmessage
┌─────────────────────────────────────────────────────┐
│ Dedicated Worker (专用 Worker) │
│ • 密集型计算 • 数据处理 • 算法执行 │
└─────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────┐
│ Shared Worker (共享 Worker) │
│ • 多页面/标签页通信 • 共享状态 • 数据缓存 │
└─────────────────────────────────────────────────────┘
🚀 基础 Worker 使用
// ============ 1. 创建 Worker ============
// main.js
const worker = new Worker('worker.js');
// 发送数据
worker.postMessage({
type: 'CALCULATE',
payload: { data: largeArray, options: { threshold: 100 } }
});
// 接收结果
worker.onmessage = (event) => {
console.log('Worker 返回结果:', event.data);
updateUI(event.data);
};
// 错误处理
worker.onerror = (error) => {
console.error('Worker 错误:', error);
showErrorMessage(error.message);
};
// 终止 Worker
// worker.terminate();
// ============ worker.js ============
self.onmessage = function(event) {
const { type, payload } = event.data;
switch (type) {
case 'CALCULATE':
const result = performHeavyCalculation(payload);
self.postMessage({ type: 'RESULT', payload: result });
break;
case 'PROGRESS':
// 进度报告
self.postMessage({ type: 'PROGRESS', payload: { percent: 50 } });
break;
default:
console.warn('未知消息类型:', type);
}
};
function performHeavyCalculation(data) {
// 耗时计算
let result = 0;
for (let i = 0; i < data.length; i++) {
result += Math.sqrt(data[i]);
}
return result;
}
📊 高性能数据处理实战
// ============ 大数据处理 Worker ============
// data-processor.worker.js
// 1. 数据分块处理
class ChunkProcessor {
constructor(data, chunkSize = 10000) {
this.data = data;
this.chunkSize = chunkSize;
this.currentChunk = 0;
this.results = [];
}
async processChunk(processor) {
const totalChunks = Math.ceil(this.data.length / this.chunkSize);
while (this.currentChunk < totalChunks) {
const start = this.currentChunk * this.chunkSize;
const end = Math.min(start + this.chunkSize, this.data.length);
const chunk = this.data.slice(start, end);
// 处理当前块
const chunkResult = processor(chunk);
this.results.push(chunkResult);
// 报告进度
const percent = (this.currentChunk / totalChunks) * 100;
self.postMessage({ type: 'PROGRESS', payload: percent });
this.currentChunk++;
// 让出执行权,避免阻塞
await new Promise(resolve => setTimeout(resolve, 0));
}
return this.mergeResults(this.results);
}
mergeResults(results) {
// 合并各块结果
if (Array.isArray(results[0])) {
return results.flat();
}
if (typeof results[0] === 'number') {
return results.reduce((a, b) => a + b, 0);
}
return results;
}
}
// 2. 图像处理 Worker
class ImageProcessor {
static grayscale(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
data[i] = gray; // R
data[i + 1] = gray; // G
data[i + 2] = gray; // B
}
return imageData;
}
static blur(imageData, radius = 3) {
const width = imageData.width;
const height = imageData.height;
const data = imageData.data;
const output = new Uint8ClampedArray(data.length);
const kernel = this.gaussianKernel(radius);
const half = Math.floor(radius / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0;
let weightSum = 0;
for (let ky = -half; ky <= half; ky++) {
for (let kx = -half; kx <= half; kx++) {
const px = Math.min(width - 1, Math.max(0, x + kx));
const py = Math.min(height - 1, Math.max(0, y + ky));
const idx = (py * width + px) * 4;
const weight = kernel[ky + half][kx + half];
r += data[idx] * weight;
g += data[idx + 1] * weight;
b += data[idx + 2] * weight;
weightSum += weight;
}
}
const outIdx = (y * width + x) * 4;
output[outIdx] = r / weightSum;
output[outIdx + 1] = g / weightSum;
output[outIdx + 2] = b / weightSum;
output[outIdx + 3] = data[outIdx + 3];
}
}
return new ImageData(output, width, height);
}
static gaussianKernel(size) {
const sigma = size / 3;
const kernel = [];
let sum = 0;
for (let i = 0; i < size; i++) {
kernel[i] = [];
for (let j = 0; j < size; j++) {
const x = i - Math.floor(size / 2);
const y = j - Math.floor(size / 2);
const value = Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
kernel[i][j] = value;
sum += value;
}
}
// 归一化
for (let i = 0; i < size; i++) {
for (let j = 0; j < size; j++) {
kernel[i][j] /= sum;
}
}
return kernel;
}
}
// 3. 排序算法 Worker
class SortingWorker {
static async quickSort(arr, depth = 0) {
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (i === Math.floor(arr.length / 2)) continue;
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 并行排序左右部分
const [sortedLeft, sortedRight] = await Promise.all([
this.quickSort(left, depth + 1),
this.quickSort(right, depth + 1)
]);
return [...sortedLeft, pivot, ...sortedRight];
}
static mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = this.mergeSort(arr.slice(0, mid));
const right = this.mergeSort(arr.slice(mid));
return this.merge(left, right);
}
static merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
}
// 4. 主 Worker 入口
self.onmessage = async function(event) {
const { id, type, payload } = event.data;
const startTime = performance.now();
try {
let result;
switch (type) {
case 'PROCESS_DATA':
const processor = new ChunkProcessor(payload.data, payload.chunkSize);
result = await processor.processChunk(payload.processor);
break;
case 'IMAGE_GRAYSCALE':
result = ImageProcessor.grayscale(payload);
break;
case 'IMAGE_BLUR':
result = ImageProcessor.blur(payload.imageData, payload.radius);
break;
case 'SORT_QUICK':
result = await SortingWorker.quickSort(payload);
break;
case 'SORT_MERGE':
result = SortingWorker.mergeSort(payload);
break;
case 'MATRIX_MULTIPLY':
result = matrixMultiply(payload.a, payload.b);
break;
default:
throw new Error(`未知操作类型: ${type}`);
}
const duration = performance.now() - startTime;
self.postMessage({
id,
type: 'RESULT',
payload: result,
duration,
status: 'success'
});
} catch (error) {
self.postMessage({
id,
type: 'ERROR',
error: error.message,
status: 'failed'
});
}
};
// 矩阵乘法(并行优化)
function matrixMultiply(a, b) {
const rows = a.length;
const cols = b[0].length;
const result = new Array(rows);
for (let i = 0; i < rows; i++) {
result[i] = new Array(cols);
for (let j = 0; j < cols; j++) {
let sum = 0;
for (let k = 0; k < a[0].length; k++) {
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
🔄 Worker Pool 管理
// ============ Worker 池 ============
class WorkerPool {
constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
this.workerScript = workerScript;
this.poolSize = poolSize;
this.workers = [];
this.taskQueue = [];
this.activeWorkers = new Set();
this.taskCallbacks = new Map();
this.nextTaskId = 0;
this.init();
}
init() {
for (let i = 0; i < this.poolSize; i++) {
const worker = new Worker(this.workerScript);
worker.onmessage = (event) => {
const { id, type, payload, duration, error } = event.data;
const callback = this.taskCallbacks.get(id);
if (callback) {
if (type === 'RESULT') {
callback.resolve({ payload, duration });
} else if (type === 'ERROR') {
callback.reject(new Error(error));
}
this.taskCallbacks.delete(id);
}
this.activeWorkers.delete(worker);
this.processNextTask(worker);
};
worker.onerror = (error) => {
console.error('Worker 错误:', error);
this.activeWorkers.delete(worker);
this.recycleWorker(worker);
};
this.workers.push(worker);
}
}
processNextTask(worker) {
if (this.taskQueue.length === 0) return;
const { id, task, resolve, reject } = this.taskQueue.shift();
this.activeWorkers.add(worker);
this.taskCallbacks.set(id, { resolve, reject });
worker.postMessage({ id, ...task });
}
execute(task) {
return new Promise((resolve, reject) => {
const id = this.nextTaskId++;
const availableWorker = this.workers.find(w => !this.activeWorkers.has(w));
if (availableWorker) {
this.activeWorkers.add(availableWorker);
this.taskCallbacks.set(id, { resolve, reject });
availableWorker.postMessage({ id, ...task });
} else {
this.taskQueue.push({ id, task, resolve, reject });
}
});
}
recycleWorker(worker) {
const index = this.workers.indexOf(worker);
if (index !== -1) {
const newWorker = new Worker(this.workerScript);
this.workers[index] = newWorker;
worker.terminate();
}
}
async map(array, processor) {
const chunkSize = Math.ceil(array.length / this.poolSize);
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
const promises = chunks.map(chunk =>
this.execute({
type: 'PROCESS_DATA',
payload: { data: chunk, processor: processor.toString() }
})
);
const results = await Promise.all(promises);
return results.flat();
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.workers = [];
this.taskQueue = [];
this.activeWorkers.clear();
this.taskCallbacks.clear();
}
}
// 使用 Worker Pool
const pool = new WorkerPool('data-processor.worker.js', 8);
// 并行处理大数据
const largeArray = Array.from({ length: 1000000 }, (_, i) => Math.random());
const results = await pool.map(largeArray, (chunk) => {
return chunk.map(x => Math.sqrt(x) * Math.sin(x));
});
console.log('处理完成:', results.length);
📡 Shared Worker 多页面通信
// ============ Shared Worker ============
// shared-worker.js
const connections = new Set();
let sharedData = {
counter: 0,
user: null,
theme: 'light'
};
self.onconnect = function(event) {
const port = event.ports[0];
connections.add(port);
port.onmessage = function(event) {
const { type, payload, clientId } = event.data;
switch (type) {
case 'GET_STATE':
port.postMessage({ type: 'STATE', payload: sharedData });
break;
case 'UPDATE_STATE':
sharedData = { ...sharedData, ...payload };
// 广播给所有连接的页面
broadcast({ type: 'STATE_CHANGED', payload: sharedData });
break;
case 'SUBSCRIBE':
// 订阅事件
break;
case 'DISCONNECT':
connections.delete(port);
break;
}
};
port.start();
};
function broadcast(message) {
connections.forEach(port => {
port.postMessage(message);
});
}
// ============ main.js (多个页面共享) ============
const sharedWorker = new SharedWorker('shared-worker.js');
sharedWorker.port.start();
sharedWorker.port.onmessage = (event) => {
const { type, payload } = event.data;
switch (type) {
case 'STATE_CHANGED':
console.log('状态已更新:', payload);
updateUI(payload);
break;
case 'STATE':
console.log('初始状态:', payload);
break;
}
};
// 更新共享状态
function updateSharedState(newState) {
sharedWorker.port.postMessage({
type: 'UPDATE_STATE',
payload: newState
});
}
// 获取当前状态
sharedWorker.port.postMessage({ type: 'GET_STATE' });
🎯 今日挑战
实现一个实时数据处理仪表板,要求:
- 使用 Web Worker 处理实时数据流(每秒1000+数据点)
- 实现 Worker Pool 管理多个计算任务
- 支持数据过滤、聚合、统计运算
- 使用 Transferable Objects 优化数据传输
- 提供数据处理进度显示
- 支持导出处理结果
// 使用示例
const dashboard = new RealtimeDashboard({
workerPool: 4,
dataSource: WebSocketStream,
processors: [
{ type: 'filter', condition: (v) => v > threshold },
{ type: 'aggregate', window: '1s', method: 'average' },
{ type: 'anomaly', algorithm: '3-sigma' }
]
});
dashboard.on('data', (processed) => {
chart.update(processed);
});
dashboard.start();
明日预告:WebSocket 高级应用 - 实时协作、游戏同步与大规模消息推送
💡 多线程箴言:"Worker 不是银弹,但它是突破 JavaScript 单线程限制的关键武器"——合理分配任务,让 UI 永远流畅!