Web Worker

发布于:2025-09-02 ⋅ 阅读:(15) ⋅ 点赞:(0)

个人简介

👀个人主页: 前端杂货铺
🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍍前端面试宝典 🎨100个小功能 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js
🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

Web Worker API

Web Worker 使得在一个独立于 Web 应用程序主执行线程的后台线程中运行脚本操作成为可能。这样做的好处是可以在独立线程中执行费时的处理任务使主线程(通常是 UI 线程)的运行不会被阻塞/放慢

Web Worker 概念与用法

Worker 是一个使用构造函数创建的对象(例如 Worker()),它运行一个具名 JavaScript 文件——该文件包含将在 worker 线程中运行的代码

除了标准的 JavaScript 函数集(如 String、Array、Object、JSON 等),你可以在 worker 线程中运行任何你喜欢的代码。

但有一些例外:

  • 不能直接在 worker 线程中操作 DOM 元素。
  • 不能使用 window 对象中的某些方法和属性。

数据通过消息系统在 worker 和主线程之间发送——双方都使用 postMessage() 方法发送消息,并通过 onmessage 事件处理程序响应消息(消息包含在 message 事件的 data 属性中)。数据是复制的,而不是共享的

Worker 全局上下文和函数

worker 在一个与当前 window 不同的全局上下文中运行!虽然 Window 不能直接用于 worker,但许多相同的方法被定义在一个共享的混入(WindowOrWorkerGlobalScope)中,并通过 worker 自己的 WorkerGlobalScope 衍生的上下文提供给它们:

  • DedicatedWorkerGlobalScope 用于专用 worker
  • SharedWorkerGlobalScope 用于共享 worker
  • ServiceWorkerGlobalScope 用于 service worker

所有 worker 和主线程(来自 WindowOrWorkerGlobalScope)共有的一些函数(子集)是:

  • WorkerGlobalScope.atob()
  • WorkerGlobalScope.btoa()
  • WorkerGlobalScope.clearInterval()
  • WorkerGlobalScope.clearTimeout()
  • WorkerGlobalScope.createImageBitmap()
  • WorkerGlobalScope.dump() 非标准
  • WorkerGlobalScope.fetch()
  • WorkerGlobalScope.queueMicrotask()
  • WorkerGlobalScope.reportError()
  • WorkerGlobalScope.setInterval()
  • WorkerGlobalScope.setTimeout()
  • WorkerGlobalScope.structuredClone()
  • DedicatedWorkerGlobalScope.requestAnimationFrame()(仅专用 worker)
  • DedicatedWorkerGlobalScope.cancelAnimationFrame()(仅专用 worker)

以下函数仅对 worker 可用:

  • WorkerGlobalScope.importScripts()(所有 worker)
  • DedicatedWorkerGlobalScope.postMessage()(仅限专用 worker)

浏览器兼容性

判断你的浏览器是否支持 Worker。

在这里插入图片描述

在这里插入图片描述

Web Worker 作用、局限性、适用场景

作用:

  • 避免UI阻塞:将耗时任务移到后台线程执行
  • 提高性能:利用多核CPU并行处理任务
  • 保持响应性:确保用户界面始终流畅响应

局限性:

  • 不能直接操作DOM
  • 无法访问 window 对象和 document 对象
  • 受到同源策略限制
  • 通信通过消息传递,数据是复制而非共享
  • 不是所有浏览器都支持所有类型的 Worker

适用场景:

  • 复杂数学计算(如加密解密、图像处理)
  • 大数据集处理与排序
  • 高频用户输入处理(如实时搜索)
  • 预加载和缓存数据
  • 轮询和服务端通信

基于复杂运算分析Web Worker的用途

我们使用斐波那契数列,进行 主线程 VS Worker 分析计算过程中 UI线程 的阻塞程度。

斐波那契数列是指这样一个数列:0,1,1,2,3,5,8,13,21,34,55,89……这个数列从第3项开始 ,每一项都等于前两项之和。

核心代码(index.js):

// 当 HTML 文档完全解析,且所有延迟脚本(<script defer src="…"> 和 <script type="module">)下载和执行完毕后,会触发 DOMContentLoaded 事件。
document.addEventListener("DOMContentLoaded", function () {
  // 获取页面中的所有相关 DOM 元素,便于后续操作
  const numberInput = document.getElementById("number");
  const numberInputWorker = document.getElementById("number-worker");
  const calculateMainBtn = document.getElementById("calculate-main");
  const calculateWorkerBtn = document.getElementById("calculate-worker");
  const resultMain = document.getElementById("result-main");
  const resultWorker = document.getElementById("result-worker");
  const timeMain = document.getElementById("time-main");
  const timeWorker = document.getElementById("time-worker");
  const statusMain = document.getElementById("status-main");
  const statusWorker = document.getElementById("status-worker");
  const uiStatusMain = document.getElementById("ui-status-main");
  const uiStatusWorker = document.getElementById("ui-status-worker");
  const barMain = document.getElementById("bar-main");
  const barWorker = document.getElementById("bar-worker");

  // 斐波那契数列递归实现(低效,仅用于演示计算消耗)
  // 输入 n,返回第 n 项斐波那契数
  function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
  }

  // 初始化 UI 拖动测试,分别用于主线程和 Worker 区域
  initDragTest("animation-main", "ui-status-main");
  initDragTest("animation-worker", "ui-status-worker");

  // 主线程计算按钮点击事件
  // 在主线程直接计算斐波那契数,阻塞 UI
  calculateMainBtn.addEventListener("click", function () {
    // 获取输入值并校验
    const n = parseInt(numberInput.value);
    if (isNaN(n) || n < 1) {
      alert("请输入有效的数字");
      return;
    }

    // 更新主线程区域的状态显示
    statusMain.textContent = "状态:计算中...";
    resultMain.textContent = "结果:计算中...";
    timeMain.textContent = "耗时:--";
    uiStatusMain.textContent = "UI响应:检测中...";

    // 禁用两个按钮,防止重复点击
    calculateMainBtn.disabled = true;
    calculateWorkerBtn.disabled = true;

    // 记录开始时间
    const startTime = performance.now();

    // 用 setTimeout 延迟,确保 UI 状态能及时刷新
    setTimeout(() => {
      try {
        // 计算斐波那契数,主线程阻塞
        const result = fibonacci(n);
        const endTime = performance.now();
        const timeTaken = (endTime - startTime).toFixed(2);

        // 显示结果和耗时
        resultMain.textContent = `结果:斐波那契(${n}) = ${result}`;
        timeMain.textContent = `耗时:${timeTaken} 毫秒`;
        statusMain.textContent = "状态:计算完成";

        // 更新性能对比图表(主线程)
        updateChart(parseFloat(timeTaken), 0);
      } catch (e) {
        // 错误处理
        statusMain.textContent = "状态:计算错误";
        resultMain.textContent = "结果:计算失败";
      } finally {
        // 恢复按钮和 UI 状态
        calculateMainBtn.disabled = false;
        calculateWorkerBtn.disabled = false;
        uiStatusMain.textContent = "UI响应:正常";
      }
    }, 100);
  });

  // Worker 线程计算按钮点击事件
  // 使用 Web Worker 进行斐波那契计算,不阻塞主线程
  calculateWorkerBtn.addEventListener("click", function () {
    // 获取输入值并校验
    const n = parseInt(numberInputWorker.value);
    if (isNaN(n) || n < 1) {
      alert("请输入有效的数字");
      return;
    }

    // 更新 Worker 区域的状态显示
    statusWorker.textContent = "状态:计算中...";
    resultWorker.textContent = "结果:计算中...";
    timeWorker.textContent = "耗时:--";
    uiStatusWorker.textContent = "UI响应:正常";

    // 禁用两个按钮,防止重复点击
    calculateMainBtn.disabled = true;
    calculateWorkerBtn.disabled = true;

    // 记录开始时间
    const startTime = performance.now();

    // 使用独立文件创建 Worker,便于调试
    const worker = new Worker("fib-worker.js");

    // 或者动态创建 Worker 代码,避免单独文件
    // const workerCode = `
    //   // Worker 内部的斐波那契递归实现
    //   function fibonacci(n) {
    //     if (n <= 1) return n;
    //     return fibonacci(n - 1) + fibonacci(n - 2);
    //   }
    //   // 监听主线程消息,计算后返回结果
    //   self.addEventListener('message', function(e) {
    //     const n = e.data;
    //     const result = fibonacci(n);
    //     self.postMessage(result);
    //   });
    // `;

    // // 通过 Blob 创建 Worker 实例
    // const blob = new Blob([workerCode], { type: "text/javascript" });
    // const worker = new Worker(URL.createObjectURL(blob));

    // 向 Worker 发送数据,开始计算
    worker.postMessage(n);

    // 监听 Worker 返回结果
    worker.onmessage = function (e) {
      const result = e.data;
      const endTime = performance.now();
      const timeTaken = (endTime - startTime).toFixed(2);

      // 显示结果和耗时
      resultWorker.textContent = `结果:斐波那契(${n}) = ${result}`;
      timeWorker.textContent = `耗时:${timeTaken} 毫秒`;
      statusWorker.textContent = "状态:计算完成";

      calculateMainBtn.disabled = false;
      calculateWorkerBtn.disabled = false;

      // 更新性能对比图表(Worker)
      updateChart(0, parseFloat(timeTaken));

      // 关闭 Worker
      worker.terminate();
    };

    // Worker 错误处理
    worker.onerror = function (e) {
      statusWorker.textContent = "状态:Worker错误";
      resultWorker.textContent = "结果:计算失败";
      calculateMainBtn.disabled = false;
      calculateWorkerBtn.disabled = false;
      uiStatusWorker.textContent = "UI响应:正常";
    };
  });

  // 拖动测试功能,检测 UI 响应流畅性
  // elementId: 可拖动元素的 id
  // statusId: 状态显示元素的 id
  function initDragTest(elementId, statusId) {
    const element = document.getElementById(elementId);
    const statusElement = document.getElementById(statusId);

    let isDragging = false;
    let offsetX, offsetY;
    let dragCount = 0;

    // 鼠标按下,开始拖动
    element.addEventListener("mousedown", function (e) {
      isDragging = true;
      offsetX = e.clientX - element.getBoundingClientRect().left;
      offsetY = e.clientY - element.getBoundingClientRect().top;
      element.style.cursor = "grabbing";
      element.style.animation = "none";
    });

    // 鼠标移动,更新元素位置
    document.addEventListener("mousemove", function (e) {
      if (isDragging) {
        dragCount++;
        element.style.position = "absolute";
        element.style.left = e.pageX - offsetX + "px";
        element.style.top = e.pageY - offsetY + "px";
        statusElement.textContent = `UI响应:正常 (拖动次数: ${dragCount})`;
      }
    });

    // 鼠标松开,结束拖动
    document.addEventListener("mouseup", function () {
      if (isDragging) {
        isDragging = false;
        element.style.cursor = "grab";
        element.style.animation = "bounce 1s infinite alternate";
      }
    });
  }

  // 更新性能对比图表
  // mainTime: 主线程耗时,workerTime: Worker 耗时
  function updateChart(mainTime, workerTime) {
    const maxHeight = 180; // 图表最大高度(像素)
    let maxTime = Math.max(mainTime, workerTime, 1);

    // 设置主线程柱状高度
    if (mainTime > 0) {
      const height = (mainTime / maxTime) * maxHeight;
      barMain.style.height = `${height}px`;
    }

    // 设置 Worker 柱状高度
    if (workerTime > 0) {
      const height = (workerTime / maxTime) * maxHeight;
      barWorker.style.height = `${height}px`;
    }
  }

  // 同步两个输入框的值,保持主线程和 Worker 输入一致
  numberInput.addEventListener("input", function () {
    numberInputWorker.value = numberInput.value;
  });

  numberInputWorker.addEventListener("input", function () {
    numberInput.value = numberInputWorker.value;
  });
});

worker代码(fib-worker.js

// fib-worker.js
// 斐波那契递归计算的 Web Worker 脚本,可独立调试

// 斐波那契递归实现
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

// 监听主线程消息,计算后返回结果
self.addEventListener("message", function (e) {
  const n = e.data;
  const result = fibonacci(n);
  // 发送结果回主线程
  self.postMessage(result);
});

通过下面的操作演示我们可以看出来,在不使用worker计算斐波那契数列时无法拖拽小球(UI被阻塞了),但使用worker就可以自由拖拽。

在这里插入图片描述

其他代码:

style.css

      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }
      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        line-height: 1.6;
        color: #333;
        background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
        min-height: 100vh;
        padding: 20px;
      }
      .container {
        max-width: 1200px;
        margin: 0 auto;
      }
      header {
        text-align: center;
        margin-bottom: 30px;
        padding: 20px;
        background: white;
        border-radius: 10px;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
      }
      h1 {
        color: #2c3e50;
        margin-bottom: 10px;
      }
      .description {
        color: #7f8c8d;
        max-width: 800px;
        margin: 0 auto;
      }
      .comparison {
        display: flex;
        gap: 20px;
        margin-bottom: 30px;
        flex-wrap: wrap;
      }
      .panel {
        flex: 1;
        min-width: 300px;
        background: white;
        border-radius: 10px;
        padding: 20px;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
      }
      .panel h2 {
        color: #2c3e50;
        padding-bottom: 10px;
        border-bottom: 2px solid #eee;
        margin-bottom: 20px;
      }
      .input-group {
        margin-bottom: 20px;
      }
      label {
        display: block;
        margin-bottom: 8px;
        font-weight: 600;
        color: #34495e;
      }
      input {
        width: 100%;
        padding: 12px;
        border: 1px solid #ddd;
        border-radius: 6px;
        font-size: 16px;
      }
      .buttons {
        display: flex;
        gap: 10px;
        margin-bottom: 20px;
      }
      button {
        flex: 1;
        padding: 12px;
        border: none;
        border-radius: 6px;
        background: #3498db;
        color: white;
        font-size: 16px;
        cursor: pointer;
        transition: all 0.3s;
      }
      button:hover {
        background: #2980b9;
        transform: translateY(-2px);
      }
      button:disabled {
        background: #95a5a6;
        cursor: not-allowed;
        transform: none;
      }
      #calculate-main {
        background: #e74c3c;
      }
      #calculate-main:hover {
        background: #c0392b;
      }
      #calculate-worker {
        background: #2ecc71;
      }
      #calculate-worker:hover {
        background: #27ae60;
      }
      .results {
        background: #f8f9fa;
        padding: 15px;
        border-radius: 6px;
        margin-bottom: 20px;
        border-left: 4px solid #3498db;
      }
      .result-item {
        margin-bottom: 10px;
      }
      .ui-test {
        background: #e8f4fc;
        padding: 20px;
        border-radius: 6px;
        text-align: center;
      }
      .animation {
        width: 60px;
        height: 60px;
        background: linear-gradient(45deg, #3498db, #2ecc71);
        border-radius: 50%;
        margin: 15px auto;
        animation: bounce 1s infinite alternate;
        cursor: grab;
        box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
      }
      @keyframes bounce {
        from {
          transform: translateY(0) scale(1);
        }
        to {
          transform: translateY(-15px) scale(1.05);
        }
      }
      .performance {
        background: white;
        border-radius: 10px;
        padding: 20px;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
      }
      .performance h2 {
        color: #2c3e50;
        margin-bottom: 20px;
        text-align: center;
      }
      .chart {
        display: flex;
        height: 200px;
        align-items: flex-end;
        justify-content: center;
        gap: 30px;
        padding: 20px;
        border-radius: 8px;
        background: #f8f9fa;
      }
      .bar-container {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      .bar {
        width: 60px;
        background: linear-gradient(to top, #3498db, #2ecc71);
        border-radius: 4px 4px 0 0;
        transition: height 1s;
      }
      .bar-label {
        margin-top: 10px;
        font-weight: 600;
      }
      .explanation {
        margin-top: 30px;
        padding: 20px;
        background: white;
        border-radius: 10px;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
      }
      .explanation h2 {
        color: #2c3e50;
        margin-bottom: 15px;
      }
      .points {
        display: flex;
        flex-wrap: wrap;
        gap: 20px;
        margin-top: 20px;
      }
      .point {
        flex: 1;
        min-width: 250px;
        padding: 15px;
        background: #f8f9fa;
        border-radius: 8px;
      }
      .point h3 {
        color: #2c3e50;
        margin-bottom: 10px;
        display: flex;
        align-items: center;
      }
      .point h3::before {
        content: "•";
        margin-right: 8px;
        color: #3498db;
        font-size: 24px;
      }
      @media (max-width: 768px) {
        .comparison {
          flex-direction: column;
        }
      }

worker-demo.html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Worker 性能分析与比较</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div class="container">
      <header>
        <h1>Web Worker 性能分析与比较</h1>
        <p class="description">
          比较使用Web
          Worker与在主线程中执行计算密集型任务的性能差异和用户体验影响
        </p>
      </header>

      <div class="comparison">
        <div class="panel">
          <h2>不使用 Web Worker</h2>
          <div class="input-group">
            <label for="number">计算斐波那契数列的第 n 项:</label>
            <input type="number" id="number" value="42" min="1" max="50" />
          </div>
          <div class="buttons">
            <button id="calculate-main">在主线程计算</button>
          </div>
          <div class="results">
            <div class="result-item" id="result-main">结果:等待计算...</div>
            <div class="result-item" id="time-main">耗时:--</div>
            <div class="result-item" id="status-main">状态:就绪</div>
          </div>
          <div class="ui-test">
            <p>UI 响应测试(计算过程中尝试拖动球体):</p>
            <div class="animation" id="animation-main" draggable="true"></div>
            <p id="ui-status-main">UI响应:正常</p>
          </div>
        </div>

        <div class="panel">
          <h2>使用 Web Worker</h2>
          <div class="input-group">
            <label for="number-worker">计算斐波那契数列的第 n 项:</label>
            <input
              type="number"
              id="number-worker"
              value="42"
              min="1"
              max="50"
            />
          </div>
          <div class="buttons">
            <button id="calculate-worker">使用 Web Worker 计算</button>
          </div>
          <div class="results">
            <div class="result-item" id="result-worker">结果:等待计算...</div>
            <div class="result-item" id="time-worker">耗时:--</div>
            <div class="result-item" id="status-worker">状态:就绪</div>
          </div>
          <div class="ui-test">
            <p>UI 响应测试(计算过程中尝试拖动球体):</p>
            <div class="animation" id="animation-worker" draggable="true"></div>
            <p id="ui-status-worker">UI响应:正常</p>
          </div>
        </div>
      </div>

      <div class="performance">
        <h2>性能比较</h2>
        <div class="chart">
          <div class="bar-container">
            <div class="bar" id="bar-main" style="height: 10px"></div>
            <div class="bar-label">主线程</div>
          </div>
          <div class="bar-container">
            <div class="bar" id="bar-worker" style="height: 10px"></div>
            <div class="bar-label">Web Worker</div>
          </div>
        </div>
      </div>

      <div class="explanation">
        <h2>为什么计算时间可能相似?</h2>
        <p>
          对于纯粹的计算密集型任务,使用Web
          Worker可能不会减少总计算时间,甚至可能由于通信开销而稍慢一些。但关键优势在于:
        </p>

        <div class="points">
          <div class="point">
            <h3>UI响应性</h3>
            <p>
              使用Web
              Worker时,UI线程不会被阻塞,用户可以继续与页面交互,提供更好的用户体验。
            </p>
          </div>
          <div class="point">
            <h3>多核利用</h3>
            <p>
              Web
              Worker允许浏览器利用多核CPU并行处理任务,对于可并行化的工作负载,可以显著减少总计算时间。
            </p>
          </div>
          <div class="point">
            <h3>避免页面卡顿</h3>
            <p>
              在主线程执行长时间任务会导致页面冻结,影响动画、输入响应和用户体验。
            </p>
          </div>
        </div>
      </div>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

写在最后

通过使用主线程和web-worker进行的比较,我们直观的认识到worker对于UI线程阻塞的影响程度要远小于主线程。

在处理复杂的数学计算、算法处理等计算密集型任务时,若还要保持UI响应性,那么web-worker确实是一个非常好的选择。


好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!

参考资料:

  1. 百度 · 百科
  2. DeepSeek
  3. MDN · Web Worker API
  4. VS Code · Copilot

在这里插入图片描述