webWorker

4 阅读2分钟

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