1. Web WorkersHTML5 引入的核心 API,用于解决 JavaScript 单线程模型下 “主线程阻塞” 问题;
  2. 它允许在后台线程中执行脚本,与主线程 (UI 线程) 并行运行,避免复杂计算、大数据处理等操作阻塞页面渲染和交互;

核心概念

  1. 线程模型基础

    1. JavaScript 主线程:负责执行 DOM 操作、事件处理、渲染、同步 JS 代码,单线程运行 (同一时间只能处理一个任务)
    2. 后台线程 (Worker 线程):由 Web Workers 创建的独立线程,不能访问 DOMBOM 核心 API,仅能通过消息与主线程通信,专注于计算密集型任务;
    3. 通信机制:主线程与 Worker 线程通过 “消息传递” 交互 (基于结构化克隆算法,自动序列化 / 反序列化数据),而非共享内存 (默认)
  2. Web Workers 分类

    类型 作用范围 核心特点
    Dedicated Worker 专用 Worker,单页面独享 一个主线程对应一个 Worker,双向通信
    Shared Worker 共享 Worker,多页面共享 多个同源页面可连接同一个 Worker,需通过端口通信
    Service Worker 服务 Worker,离线 / 代理 运行在后台,用于缓存、推送通知、拦截请求 (PWA 核心)

核心使用场景

  1. Web Workers 适用于 CPU 密集型任务 (避免阻塞主线程),典型场景:

    1. 大数据处理 (如数组排序、过滤、统计)
    2. 复杂算法计算 (如加密解密、数学建模、图形学计算)
    3. 数据解析 (如 CSV/JSON 大文件解析、Excel 导出)
    4. 循环密集型操作 (如大量数据格式化、正则匹配)
  2. ❌ 不适用于:

    1. DOM/BOM 操作 (如操作 document、window、alert)
    2. 同步阻塞主线程的需求 (Worker 是异步通信)
    3. 简单计算 (通信开销可能大于计算收益)

Dedicated Worker 核心 API 详解

主线程 API

  1. 创建 Worker 实例:通过 new Worker(scriptURL) 创建 Worker 线程,scriptURL 必须是同源脚本文件 (不能是本地文件 file:// 协议,需通过 HTTP/HTTPS 访问)

    // 主线程:创建 Worker(worker.js 是后台脚本路径)
    const worker = new Worker('worker.js');
    
  2. 发送消息worker.postMessage(data),向 Worker 线程发送数据,data 支持:

    1. 基本类型 (字符串、数字、布尔、null/undefined)
    2. 复杂类型 (对象、数组、Date、RegExp、Blob、ArrayBuffer 等)
    3. 不支持:函数、DOM 元素、循环引用对象 (会抛出 DataCloneError)
    // 主线程:发送数据给 Worker
    const userData = { id: 1, name: '张三', data: [1, 2, 3, 4] };
    worker.postMessage(userData); // 自动序列化数据
    
  3. 接收消息worker.onmessage,监听 Worker 线程的消息回调,消息内容通过 event.data 获取;

    // 主线程:接收 Worker 的响应
    worker.onmessage = (event) => {
      console.log('Worker 返回结果:', event.data);
      // 处理结果(如更新 DOM)
      document.getElementById('result').textContent = event.data;
    };
    
    // 或使用 addEventListener(推荐,支持多个回调)
    worker.addEventListener('message', (event) => {
      console.log('接收消息:', event.data);
    });
    
  4. 错误处理worker.onerror,监听 Worker 线程的错误 (语法错误、运行时错误),错误信息包含 filename (出错脚本)lineno (行号)colno (列号)message (错误描述)

    // 主线程:监听 Worker 错误
    worker.onerror = (error) => {
      console.error(`Worker 错误:${error.message}(${error.filename}:${error.lineno})`);
      error.preventDefault(); // 阻止错误冒泡到主线程(可选)
    };
    
  5. 终止 Workerworker.terminate(),主动关闭 Worker 线程,释放资源 (Worker 线程无法感知自身被终止,也无法执行清理操作)

    // 主线程:终止 Worker(如页面卸载、任务完成后)
    worker.terminate();
    

Worker 线程 API

Worker 线程的全局对象不是 window,而是 self(或 WorkerGlobalScope 实例),核心 API 如下:

  1. 接收消息self.onmessage,监听主线程发送的消息,逻辑与主线程一致;

    // worker.js(Worker 线程脚本)
    self.onmessage = (event) => {
      console.log('接收主线程消息:', event.data);
      const { data } = event;
    
      // 执行密集型任务(如排序大数据)
      const result = data.sort((a, b) => b - a); // 降序排序
    
      // 向主线程发送结果
      self.postMessage(`排序完成:${result}`);
    };
    
  2. 发送消息self.postMessage(data),与主线程 postMessage 用法一致,用于回传结果或中间状态;

  3. 错误处理self.onerrorWorker 线程内部也可监听自身错误 (可选,若未监听则错误会冒泡到主线程)

    // worker.js
    self.onerror = (error) => {
      console.error('Worker 内部错误:', error.message);
      return true; // 阻止错误冒泡到主线程
    };
    
  4. 关闭自身self.close()Worker 线程主动关闭自己 (可执行清理操作后调用)

    // worker.js
    self.onmessage = (event) => {
      const result = heavyCompute(event.data);
      self.postMessage(result);
      self.close(); // 任务完成后主动关闭
    };
    
  5. 导入脚本importScripts(url1, url2, …)

    1. Worker 线程中无法使用 import 语法 (ES 模块),需通过 importScripts 同步导入其他脚本 (支持多个 URL,按顺序加载)
    2. 注意:importScripts 导入的脚本必须与 Worker 脚本同源;
    // worker.js:导入工具脚本
    importScripts('utils.js', 'math.js'); // 同步加载并执行 utils.js 和 math.js
    
    self.onmessage = (event) => {
      const result = utils.format(math.calculate(event.data)); // 使用导入的函数
      self.postMessage(result);
    };
    

完整示例

大数据排序

  1. 示例代码:

    <!DOCTYPE html>
    <html>
    <body>
      <h1>Web Workers 大数据排序示例</h1>
      <button onclick="startSort()">开始排序(100万条数据)</button>
      <p id="status">状态:未开始</p>
      <p id="result"></p>
    
      <script>
        let worker;
    
        function startSort() {
          // 1. 创建 Worker
          worker = new Worker('sort-worker.js');
          document.getElementById('status').textContent = '状态:排序中...';
    
          // 2. 生成 100 万条随机数据(模拟大数据)
          const bigData = Array.from({ length: 1000000 }, () => Math.random() * 1000000);
    
          // 3. 发送数据给 Worker
          worker.postMessage(bigData);
    
          // 4. 接收结果
          worker.onmessage = (event) => {
            document.getElementById('status').textContent = '状态:排序完成';
            document.getElementById('result').textContent = `前 10 条数据:${event.data.slice(0, 10).join(', ')}`;
            worker.terminate(); // 终止 Worker
          };
    
          // 5. 错误处理
          worker.onerror = (error) => {
            document.getElementById('status').textContent = `错误:${error.message}`;
            worker.terminate();
          };
        }
    
        // 页面卸载时终止 Worker
        window.addEventListener('beforeunload', () => {
          if (worker) worker.terminate();
        });
      </script>
    </body>
    </html>
    
    // 接收主线程的大数据并排序
    self.onmessage = (event) => {
      const bigData = event.data;
    
      // 执行密集型排序(不会阻塞主线程)
      bigData.sort((a, b) => a - b); // 升序排序
    
      // 回传结果
      self.postMessage(bigData);
      self.close(); // 主动关闭
    };
    
  2. 运行效果:点击按钮后,页面仍可正常交互 (如滚动、点击),排序在后台完成,避免了主线程阻塞导致的页面卡顿;

多线程实现非阻塞全排列

  1. 什么是全排列:从 n 个不同元素中任取 m (m ≤ n) 个元素,按照一定的顺序排列起来,叫做从 n 个不同元素中取出 m 个元素的一个排列;当 m=n 时所有的排列情况叫全排列;

  2. 代码示例

    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JavaScript实现全排列</title>
    </head>
    <body>
        <input type="text" id="str" />
        <button onclick="combine()">全排列</button>
        结果是:<div id="result" style="width:500px;height:500px;word-break: break-all;"></div>
    </body>
    <script type="text/JavaScript">
          // 点击按钮向 webworker 线程发送请求  
          function combine() {
              var worker = new Worker('http://127.0.0.1:5500/worker.js');
              worker.postMessage(document.getElementById("str").value);
                  worker.onmessage= function (event) {                        
                    document.getElementById("result").innerHTML  =   event.data ; // 监听 JavaScript 线程的结果
                };
          }
        </script>
    </html>
    
    importScripts('script.js');
    
    // 监听主线程的数据请求
    self.onmessage = function (message) {
        var msg = message.data;
        if (msg == "") {
            postMessage("请输入正确的字符串");
        } else {
            postMessage(getGroup(msg.split("")));
        }
    }
    
    // 生成全排列
    function getGroup(data, index = 0, group = []) {
        var need_apply = new Array();
        need_apply.push(data[index]);
    
        for (var i = 0; i < group.length; i++) {
            need_apply.push(group[i] + data[index]);
        }
        group.push.apply(group, need_apply);
    
        if (index + 1 >= data.length) {
            return group;
        } else {
            return getGroup(data, index + 1, group);
        }
    }
    

进阶特性

共享内存(SharedArrayBuffer)

  1. 默认情况下,主线程与 Worker 线程的消息传递基于 “复制” (结构化克隆),大数据传递会有性能开销;SharedArrayBuffer 允许两者共享同一块内存,无需复制数据,适用于超大文件处理、实时计算等场景;

  2. ⚠️ 注意:SharedArrayBuffer 受浏览器安全策略限制 (需启用跨源隔离,设置 Cross-Origin-Opener-Policy: same-origin 和 Cross-Origin-Embedder-Policy: require-corp 响应头)

  3. 示例:

    // 主线程:创建共享内存
    const buffer = new SharedArrayBuffer(8); // 8 字节(存储一个 64 位整数)
    const arr = new Uint32Array(buffer); // 视图(操作内存)
    arr[0] = 100;
    
    const worker = new Worker('shared-worker.js');
    worker.postMessage({ buffer }); // 传递共享内存的引用
    
    // Worker 线程(shared-worker.js)
    self.onmessage = (event) => {
      const { buffer } = event.data;
      const arr = new Uint32Array(buffer);
      arr[0] += 200; // 直接修改共享内存
      self.postMessage(arr[0]); // 主线程接收 300
    };
    

转让所有权(Transferable Objects)

  1. 对于 ArrayBuffer 等二进制数据,可通过 “转让所有权” 方式传递 —— 主线程失去对数据的控制权,Worker 线程获得独占权,避免复制开销 (比共享内存更简单,无需安全策略)

  2. 示例:

    // 主线程
    const buffer = new ArrayBuffer(1024);
    console.log(buffer.byteLength); // 1024
    
    // 第二个参数指定转让的对象
    worker.postMessage({ buffer }, [buffer]);
    console.log(buffer.byteLength); // 0(主线程已失去所有权)
    
    // Worker 线程
    self.onmessage = (event) => {
      const { buffer } = event.data;
      console.log(buffer.byteLength); // 1024(获得所有权)
    };
    

Shared Worker(多页面共享)

  1. Shared Worker 允许多个同源页面共享同一个 Worker 线程,适用于多页面协作 (如实时数据同步、共享计算结果),核心差异:

    1. 通信需通过 MessagePort (端口)
    2. 需通过 SharedWorker.port 访问通信端口;
    3. 多个页面连接时,Worker 线程通过 self.onconnect 监听连接;
  2. 示例:

    // 主线程(页面 A/页面 B,同源)
    const sharedWorker = new SharedWorker('shared-worker.js');
    
    // 连接端口并监听消息
    sharedWorker.port.addEventListener('message', (event) => {
      console.log('接收共享 Worker 消息:', event.data);
    });
    sharedWorker.port.start(); // 激活端口(必须调用)
    
    // 发送消息
    sharedWorker.port.postMessage('来自页面 A 的消息');
    
    // Shared Worker 线程(shared-worker.js)
    self.onconnect = (event) => {
      const port = event.ports[0]; // 获取连接端口
    
      port.addEventListener('message', (msgEvent) => {
        console.log('接收页面消息:', msgEvent.data);
        port.postMessage(`已收到:${msgEvent.data}`); // 回传消息
      });
    
      port.start(); // 激活端口
    };
    

Service Worker(离线与代理)

  1. Service Worker 是运行在后台的 Worker,与页面无直接关联,核心用途:

    1. 离线缓存 (PWA 核心,缓存静态资源实现离线访问)
    2. 拦截网络请求 (修改请求 / 响应,实现本地 Mock、降级策略)
    3. 推送通知 (配合 Push API 实现后台消息推送)
  2. 关键特性:

    1. 基于 HTTPS (本地开发可使用 localhost)
    2. 生命周期独立于页面 (安装、激活、等待、废弃)
    3. 支持 Cache APIFetch API 等;
  3. 简单示例(缓存静态资源):

    // 主线程
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', async () => {
        try {
          await navigator.serviceWorker.register('/service-worker.js');
          console.log('Service Worker 注册成功');
        } catch (err) {
          console.error('Service Worker 注册失败:', err);
        }
      });
    }
    
    // service-worker.js
    self.addEventListener('install', (event) => {
      // 安装时缓存核心资源
      event.waitUntil(
        caches.open('v1').then((cache) => {
          return cache.addAll(['/index.html', '/style.css', '/app.js']);
        })
      );
    });
    
    self.addEventListener('fetch', (event) => {
      // 拦截请求,优先从缓存读取
      event.respondWith(
        caches.match(event.request).then((response) => {
          return response || fetch(event.request); // 缓存未命中则走网络
        })
      );
    });
    

限制与注意事项

  1. 同源限制:Worker 脚本必须与主线程页面同源 (协议、域名、端口一致),不能加载跨域脚本;

  2. 全局对象限制:Worker 线程中无 window 对象,无法访问:

    1. DOM 相关:documentelementalertconfirm
    2. BOM 相关:windowlocation (只读)navigator (部分属性)
    3. 其他:evalwith 语句、XMLHttpRequest (可使用 fetch)
  3. 通信限制:

    1. 消息传递基于结构化克隆算法,不支持函数、DOM 元素、循环引用;
    2. 大数据传递 (如 100MB 以上) 建议用 SharedArrayBuffer 或转让所有权,避免复制开销;
  4. 性能注意:

    1. 避免频繁发送小消息 (通信有开销,可批量发送)
    2. 不滥用 Worker (创建 / 销毁线程有成本,复杂任务才使用)
    3. Worker 数量不宜过多 (一般建议不超过 CPU 核心数)
  5. 调试方式:

    1. Chrome 开发者工具:Sources → Workers 面板,可断点调试 Worker 脚本;
    2. 打印日志:通过 console.log 输出到主线程控制台;

最佳实践

  1. 任务拆分:将密集型任务拆分为小块,通过消息传递中间状态,避免 Worker 线程长时间占用 CPU

  2. 资源管理

    1. 任务完成后主动调用 worker.terminate()self.close() 释放资源;
    2. 页面卸载 (beforeunload) 时终止所有 Worker
  3. 错误边界:必须监听 worker.onerror,避免 Worker 错误导致主线程异常;

  4. 兼容性处理

    1. 检测浏览器支持:if (window.Worker) { /* 使用 Worker */ }
    2. 不支持时提供降级方案 (如同步计算,或提示用户升级浏览器)
  5. 数据优化:

    1. 传递大数据时优先使用 ArrayBufferBlob 等二进制格式;
    2. 避免传递不必要的复杂对象 (精简数据结构)

面试题

什么是 Web Workers?它解决了什么问题?

  1. Web WorkersHTML5 提供的浏览器 API,允许主线程 (UI 线程) 创建后台线程 (Worker 线程),在不阻塞主线程的前提下执行耗时任务 (如大数据计算、复杂逻辑处理)

  2. 解决的核心问题:

    1. 浏览器主线程是单线程的,若执行耗时操作 (如 10 万条数据排序、复杂数学计算),会导致 UI 卡顿 (无法响应点击、滚动等事件)、页面假死;
    2. Web Workers 让耗时任务在后台线程执行,主线程专注处理 UI 交互,保证页面流畅性;

Web Workers 不能做什么?为什么?

Web Workers 为了线程安全和避免 DOM 冲突,存在以下限制 (核心原因:后台线程无法直接操作 UI,且需隔离主线程资源)

  1. 不能访问 DOM/BOM 核心对象:
    1. 无法访问 windowdocumentbodyDOM 节点 (不能操作 UI,如修改 DOM、获取 document.getElementById)
    2. 无法访问 navigator 的部分属性 (如 navigator.plugins)location 的写入操作;
  2. 不能访问主线程的全局变量 / 函数:无法直接使用主线程定义的变量、函数 (如 Vue 实例、React 组件),只能通过 postMessage 通信传递数据;
  3. 同源限制:Worker 脚本必须与主线程页面同源 (协议、域名、端口一致),不能加载跨域脚本;
  4. 不能使用 alert()/confirm(): 后台线程无 UI 上下文,无法弹出对话框;
  5. 不能操作本地文件系统:无法直接读取本地文件 (需通过主线程传递 File 对象间接处理)
  6. 单线程模型:每个 Worker 线程自身是单线程的,若 Worker 内有耗时操作,会阻塞自身,需通过创建多个 Worker 解决 (但需控制数量,避免资源占用过高)

Web Workers 为什么不能访问 DOM?

  1. 核心原因:DOM 是线程不安全的;DOM 操作 (如修改节点、获取布局) 需要同步主线程的 UI 渲染流程,若多个线程同时操作 DOM,会导致 DOM 树状态不一致 (如竞态条件),引发页面错乱、崩溃;

  2. 设计初衷:Web Workers 专注于 “计算”,主线程专注于 “UI 交互”,职责分离避免冲突,保证线程安全和页面稳定性;

Web Workers 的生命周期是什么?如何关闭 Worker?

  1. 生命周期:

    1. 主线程通过 new Worker(scriptURL) 创建 Worker,浏览器加载脚本并启动后台线程;
    2. 线程运行期间,通过 postMessage()/onmessage 通信;
    3. 线程终止:主动关闭 (主线程调用 worker.terminate() 或 Worker 自身调用 self.close()),或页面卸载 (主线程销毁时 Worker 自动终止)
  2. 关闭 Worker 的区别:

    1. 主线程关闭:worker.terminate() → 立即终止 Worker 线程,Worker 无法再发送消息,主线程也无法接收其后续消息;
    2. Worker 自身关闭:self.close()Worker 主动终止,主线程仍可继续运行,且需手动移除 onmessage 监听 (避免内存泄漏)

Web Workers 的典型应用场景有哪些?

  1. 适用于 CPU 密集型任务 (避免阻塞主线程),常见场景:

    1. 大数据处理 / 计算:如 10 万条数据排序、过滤、统计 (如表格数据筛选)
    2. 复杂算法:如加密 / 解密 (AES/RSA)、数据压缩 (GZIP)、数学建模 (如三角函数计算)
    3. 离线数据处理:如解析大文件 (CSV/Excel)、处理 Blob 数据 (如图片压缩前的像素处理)
    4. 实时数据处理:如 WebSocket 接收的海量数据解析 (如实时监控数据)
    5. 缓存预处理:如 Service Workers 中提前缓存静态资源 (PWA 核心)
  2. 反例:不适用于 I/O 密集型任务 (如网络请求、定时器),这类任务主线程异步处理即可,无需 Worker

使用 Web Workers 时需要注意哪些优化点?

  1. 控制 Worker 数量:每个 Worker 占用独立线程和内存,过多 Worker 会导致线程切换开销增大,建议根据 CPU 核心数创建 (一般不超过 4 个)

  2. 减少通信频率:postMessage() 序列化 / 反序列化有开销,避免频繁传递大量数据 (如可批量处理数据后一次性发送)

  3. 使用 ArrayBuffer 共享数据:对于超大二进制数据 (如图片、视频),使用 ArrayBuffer 传递 (结构化克隆算法对其优化,避免深拷贝,仅传递引用),提升性能;

  4. 避免 Worker 闲置:不需要时主动关闭 Worker (terminate()/close()),释放内存和线程资源;

  5. 兼容处理:低版本浏览器 (如 IE10 以下) 不支持 Web Workers,需通过 if (window.Worker) 做降级处理;

  6. 脚本路径正确:创建 Worker 时,脚本路径是相对于主线程 HTML 文件,而非当前 JS 文件 (若路径错误会报 404)

Web Workers 与 Service Workers 的区别是什么?

维度 Web Workers(Dedicated) Service Workers
核心作用 后台执行 CPU 密集型计算,不阻塞 UI 代理网络请求、离线缓存、推送通知
生命周期 与创建它的页面绑定 (页面关闭则终止) 独立于页面 (页面关闭后仍可运行)
通信方式 与创建它的主线程直接 postMessage 与页面通过 Client API 通信
访问资源 不能访问 DOM,可访问部分 navigator 不能访问 DOM,可访问 fetch API
运行时机 页面打开后主动创建才运行 页面加载时注册,后台持续运行
典型场景 大数据计算、复杂算法 PWA 离线缓存、推送通知、后台同步

Web Workers 与 Threads.js 有什么关系?

  1. Web Workers 是浏览器原生 API,限制较多 (不能共享内存、不能访问 DOM)

  2. Threads.js 是基于 Web Workers 的封装库,简化了多线程编程 (如支持 Promise API、自动管理 Worker 池、支持传递函数),本质还是依赖浏览器的 Web Workers 能力,并未突破其核心限制;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 核心概念
  2. 2. 核心使用场景
  3. 3. Dedicated Worker 核心 API 详解
    1. 3.1. 主线程 API
    2. 3.2. Worker 线程 API
  4. 4. 完整示例
    1. 4.1. 大数据排序
    2. 4.2. 多线程实现非阻塞全排列
  5. 5. 进阶特性
    1. 5.1. 共享内存(SharedArrayBuffer)
    2. 5.2. 转让所有权(Transferable Objects)
    3. 5.3. Shared Worker(多页面共享)
    4. 5.4. Service Worker(离线与代理)
  6. 6. 限制与注意事项
  7. 7. 最佳实践
  8. 8. 面试题
    1. 8.1. 什么是 Web Workers?它解决了什么问题?
    2. 8.2. Web Workers 不能做什么?为什么?
    3. 8.3. Web Workers 为什么不能访问 DOM?
    4. 8.4. Web Workers 的生命周期是什么?如何关闭 Worker?
    5. 8.5. Web Workers 的典型应用场景有哪些?
    6. 8.6. 使用 Web Workers 时需要注意哪些优化点?
    7. 8.7. Web Workers 与 Service Workers 的区别是什么?
    8. 8.8. Web Workers 与 Threads.js 有什么关系?