Skip to content

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网络请求
WebSocketWebSocket 连接
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 创建的最佳实践

  1. 复用 Worker:避免频繁创建和销毁 Worker
  2. 控制数量:建议不超过 CPU 核心数(navigator.hardwareConcurrency
  3. 错误处理:始终监听 onerror 事件
  4. 及时终止:不使用时调用 worker.terminate() 释放资源
javascript
// 获取 CPU 核心数
const cores = navigator.hardwareConcurrency || 4;
console.log(`CPU 核心数: ${cores}`);
// 建议创建 Worker 数不超过 cores - 1

最佳实践

  1. 计算密集型任务优先使用 Worker:排序、搜索、加密、图像处理
  2. 避免短任务使用 Worker:创建 Worker 有开销,短任务得不偿失
  3. 使用 Blob URL 实现内联 Worker,减少文件依赖
  4. 通过消息传递数据,避免共享内存的复杂性

下一节

继续学习:Worker 通信

参考链接