- Web Workers 是 HTML5 引入的核心 API,用于解决 JavaScript 单线程模型下 “主线程阻塞” 问题;
- 它允许在后台线程中执行脚本,与主线程 (UI 线程) 并行运行,避免复杂计算、大数据处理等操作阻塞页面渲染和交互;
核心概念
-
线程模型基础
- JavaScript 主线程:负责执行 DOM 操作、事件处理、渲染、同步 JS 代码,单线程运行 (同一时间只能处理一个任务);
- 后台线程 (Worker 线程):由 Web Workers 创建的独立线程,不能访问 DOM 和 BOM 核心 API,仅能通过消息与主线程通信,专注于计算密集型任务;
- 通信机制:主线程与 Worker 线程通过 “消息传递” 交互 (基于结构化克隆算法,自动序列化 / 反序列化数据),而非共享内存 (默认);
-
Web Workers 分类
类型 作用范围 核心特点 Dedicated Worker 专用 Worker,单页面独享 一个主线程对应一个 Worker,双向通信 Shared Worker 共享 Worker,多页面共享 多个同源页面可连接同一个 Worker,需通过端口通信 Service Worker 服务 Worker,离线 / 代理 运行在后台,用于缓存、推送通知、拦截请求 (PWA 核心)
核心使用场景
-
Web Workers 适用于 CPU 密集型任务 (避免阻塞主线程),典型场景:
- 大数据处理 (如数组排序、过滤、统计);
- 复杂算法计算 (如加密解密、数学建模、图形学计算);
- 数据解析 (如 CSV/JSON 大文件解析、Excel 导出);
- 循环密集型操作 (如大量数据格式化、正则匹配);
-
❌ 不适用于:
- DOM/BOM 操作 (如操作 document、window、alert);
- 同步阻塞主线程的需求 (Worker 是异步通信);
- 简单计算 (通信开销可能大于计算收益);
Dedicated Worker 核心 API 详解
主线程 API
-
创建 Worker 实例:通过 new Worker(scriptURL) 创建 Worker 线程,scriptURL 必须是同源脚本文件 (不能是本地文件 file:// 协议,需通过 HTTP/HTTPS 访问);// 主线程:创建 Worker(worker.js 是后台脚本路径) const worker = new Worker('worker.js'); -
发送消息:worker.postMessage(data),向 Worker 线程发送数据,data 支持:- 基本类型 (字符串、数字、布尔、null/undefined);
- 复杂类型 (对象、数组、Date、RegExp、Blob、ArrayBuffer 等);
- 不支持:函数、DOM 元素、循环引用对象 (会抛出 DataCloneError);
// 主线程:发送数据给 Worker const userData = { id: 1, name: '张三', data: [1, 2, 3, 4] }; worker.postMessage(userData); // 自动序列化数据 -
接收消息: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); }); -
错误处理:worker.onerror,监听 Worker 线程的错误 (语法错误、运行时错误),错误信息包含 filename (出错脚本)、lineno (行号)、colno (列号)、message (错误描述);// 主线程:监听 Worker 错误 worker.onerror = (error) => { console.error(`Worker 错误:${error.message}(${error.filename}:${error.lineno})`); error.preventDefault(); // 阻止错误冒泡到主线程(可选) }; -
终止 Worker:worker.terminate(),主动关闭 Worker 线程,释放资源 (Worker 线程无法感知自身被终止,也无法执行清理操作);// 主线程:终止 Worker(如页面卸载、任务完成后) worker.terminate();
Worker 线程 API
Worker 线程的全局对象不是 window,而是 self(或 WorkerGlobalScope 实例),核心 API 如下:
-
接收消息: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}`); }; -
发送消息:self.postMessage(data),与主线程 postMessage 用法一致,用于回传结果或中间状态; -
错误处理:self.onerror,Worker 线程内部也可监听自身错误 (可选,若未监听则错误会冒泡到主线程);// worker.js self.onerror = (error) => { console.error('Worker 内部错误:', error.message); return true; // 阻止错误冒泡到主线程 }; -
关闭自身:self.close(),Worker 线程主动关闭自己 (可执行清理操作后调用);// worker.js self.onmessage = (event) => { const result = heavyCompute(event.data); self.postMessage(result); self.close(); // 任务完成后主动关闭 }; -
导入脚本:importScripts(url1, url2, …);- Worker 线程中无法使用 import 语法 (ES 模块),需通过 importScripts 同步导入其他脚本 (支持多个 URL,按顺序加载);
- 注意: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); };
完整示例
大数据排序
-
示例代码:
<!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(); // 主动关闭 }; -
运行效果:点击按钮后,页面仍可正常交互 (如滚动、点击),排序在后台完成,避免了主线程阻塞导致的页面卡顿;
多线程实现非阻塞全排列
-
什么是全排列:从 n 个不同元素中任取 m (m ≤ n) 个元素,按照一定的顺序排列起来,叫做从 n 个不同元素中取出 m 个元素的一个排列;当 m=n 时所有的排列情况叫全排列;
-
<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)
-
默认情况下,主线程与 Worker 线程的消息传递基于 “复制” (结构化克隆),大数据传递会有性能开销;SharedArrayBuffer 允许两者共享同一块内存,无需复制数据,适用于超大文件处理、实时计算等场景;
-
⚠️ 注意:SharedArrayBuffer 受浏览器安全策略限制 (需启用跨源隔离,设置 Cross-Origin-Opener-Policy: same-origin 和 Cross-Origin-Embedder-Policy: require-corp 响应头);
-
示例:
// 主线程:创建共享内存 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)
-
对于 ArrayBuffer 等二进制数据,可通过 “转让所有权” 方式传递 —— 主线程失去对数据的控制权,Worker 线程获得独占权,避免复制开销 (比共享内存更简单,无需安全策略);
-
示例:
// 主线程 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(多页面共享)
-
Shared Worker 允许多个同源页面共享同一个 Worker 线程,适用于多页面协作 (如实时数据同步、共享计算结果),核心差异:
- 通信需通过 MessagePort (端口);
- 需通过 SharedWorker.port 访问通信端口;
- 多个页面连接时,Worker 线程通过 self.onconnect 监听连接;
-
示例:
// 主线程(页面 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(离线与代理)
-
Service Worker 是运行在后台的 Worker,与页面无直接关联,核心用途:
- 离线缓存 (PWA 核心,缓存静态资源实现离线访问);
- 拦截网络请求 (修改请求 / 响应,实现本地 Mock、降级策略);
- 推送通知 (配合 Push API 实现后台消息推送);
-
关键特性:
- 基于 HTTPS (本地开发可使用 localhost);
- 生命周期独立于页面 (安装、激活、等待、废弃);
- 支持 Cache API、Fetch API 等;
-
简单示例(缓存静态资源):
// 主线程 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); // 缓存未命中则走网络 }) ); });
限制与注意事项
同源限制:Worker 脚本必须与主线程页面同源 (协议、域名、端口一致),不能加载跨域脚本;
全局对象限制:Worker 线程中无 window 对象,无法访问:
- DOM 相关:document、element、alert、confirm;
- BOM 相关:window、location (只读)、navigator (部分属性);
- 其他:eval、with 语句、XMLHttpRequest (可使用 fetch);
通信限制:
- 消息传递基于结构化克隆算法,不支持函数、DOM 元素、循环引用;
- 大数据传递 (如 100MB 以上) 建议用 SharedArrayBuffer 或转让所有权,避免复制开销;
性能注意:
- 避免频繁发送小消息 (通信有开销,可批量发送);
- 不滥用 Worker (创建 / 销毁线程有成本,复杂任务才使用);
- Worker 数量不宜过多 (一般建议不超过 CPU 核心数);
调试方式:
- Chrome 开发者工具:Sources → Workers 面板,可断点调试 Worker 脚本;
- 打印日志:通过 console.log 输出到主线程控制台;
最佳实践
-
任务拆分:将密集型任务拆分为小块,通过消息传递中间状态,避免 Worker 线程长时间占用 CPU; -
资源管理:- 任务完成后主动调用 worker.terminate() 或 self.close() 释放资源;
- 页面卸载 (beforeunload) 时终止所有 Worker;
-
错误边界:必须监听 worker.onerror,避免 Worker 错误导致主线程异常; -
兼容性处理:- 检测浏览器支持:
if (window.Worker) { /* 使用 Worker */ }; - 不支持时提供降级方案
(如同步计算,或提示用户升级浏览器);
- 检测浏览器支持:
-
数据优化:
- 传递大数据时优先使用 ArrayBuffer、Blob 等二进制格式;
- 避免传递不必要的复杂对象 (精简数据结构);
面试题
什么是 Web Workers?它解决了什么问题?
Web Workers 是 HTML5 提供的浏览器 API,允许主线程 (UI 线程) 创建后台线程 (Worker 线程),在不阻塞主线程的前提下执行耗时任务 (如大数据计算、复杂逻辑处理);
解决的核心问题:
- 浏览器主线程是单线程的,若执行耗时操作 (如 10 万条数据排序、复杂数学计算),会导致 UI 卡顿 (无法响应点击、滚动等事件)、页面假死;
- Web Workers 让耗时任务在后台线程执行,主线程专注处理 UI 交互,保证页面流畅性;
Web Workers 不能做什么?为什么?
Web Workers 为了线程安全和避免 DOM 冲突,存在以下限制 (核心原因:后台线程无法直接操作 UI,且需隔离主线程资源):
- 不能访问 DOM/BOM 核心对象:
- 无法访问 window、document、body 等 DOM 节点 (不能操作 UI,如修改 DOM、获取 document.getElementById);
- 无法访问 navigator 的部分属性 (如 navigator.plugins)、location 的写入操作;
- 不能访问主线程的全局变量 / 函数:无法直接使用主线程定义的变量、函数 (如 Vue 实例、React 组件),只能通过 postMessage 通信传递数据;
- 同源限制:Worker 脚本必须与主线程页面同源 (协议、域名、端口一致),不能加载跨域脚本;
- 不能使用 alert()/confirm(): 后台线程无 UI 上下文,无法弹出对话框;
- 不能操作本地文件系统:无法直接读取本地文件 (需通过主线程传递 File 对象间接处理);
- 单线程模型:每个 Worker 线程自身是单线程的,若 Worker 内有耗时操作,会阻塞自身,需通过创建多个 Worker 解决 (但需控制数量,避免资源占用过高);
Web Workers 为什么不能访问 DOM?
核心原因:DOM 是线程不安全的;DOM 操作 (如修改节点、获取布局) 需要同步主线程的 UI 渲染流程,若多个线程同时操作 DOM,会导致 DOM 树状态不一致 (如竞态条件),引发页面错乱、崩溃;
设计初衷:Web Workers 专注于 “计算”,主线程专注于 “UI 交互”,职责分离避免冲突,保证线程安全和页面稳定性;
Web Workers 的生命周期是什么?如何关闭 Worker?
生命周期:
- 主线程通过 new Worker(scriptURL) 创建 Worker,浏览器加载脚本并启动后台线程;
- 线程运行期间,通过 postMessage()/onmessage 通信;
- 线程终止:主动关闭 (主线程调用 worker.terminate() 或 Worker 自身调用 self.close()),或页面卸载 (主线程销毁时 Worker 自动终止);
关闭 Worker 的区别:
- 主线程关闭:worker.terminate() → 立即终止 Worker 线程,Worker 无法再发送消息,主线程也无法接收其后续消息;
- Worker 自身关闭:self.close() → Worker 主动终止,主线程仍可继续运行,且需手动移除 onmessage 监听 (避免内存泄漏);
Web Workers 的典型应用场景有哪些?
适用于 CPU 密集型任务 (避免阻塞主线程),常见场景:
- 大数据处理 / 计算:如 10 万条数据排序、过滤、统计 (如表格数据筛选);
- 复杂算法:如加密 / 解密 (AES/RSA)、数据压缩 (GZIP)、数学建模 (如三角函数计算);
- 离线数据处理:如解析大文件 (CSV/Excel)、处理 Blob 数据 (如图片压缩前的像素处理);
- 实时数据处理:如 WebSocket 接收的海量数据解析 (如实时监控数据);
- 缓存预处理:如 Service Workers 中提前缓存静态资源 (PWA 核心);
反例:不适用于 I/O 密集型任务 (如网络请求、定时器),这类任务主线程异步处理即可,无需 Worker;
使用 Web Workers 时需要注意哪些优化点?
控制 Worker 数量:每个 Worker 占用独立线程和内存,过多 Worker 会导致线程切换开销增大,建议根据 CPU 核心数创建 (一般不超过 4 个);
减少通信频率:postMessage() 序列化 / 反序列化有开销,避免频繁传递大量数据 (如可批量处理数据后一次性发送);
使用 ArrayBuffer 共享数据:对于超大二进制数据 (如图片、视频),使用 ArrayBuffer 传递 (结构化克隆算法对其优化,避免深拷贝,仅传递引用),提升性能;
避免 Worker 闲置:不需要时主动关闭 Worker (terminate()/close()),释放内存和线程资源;
兼容处理:低版本浏览器 (如 IE10 以下) 不支持 Web Workers,需通过
if (window.Worker)做降级处理;脚本路径正确:创建 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 有什么关系?
Web Workers 是浏览器原生 API,限制较多 (不能共享内存、不能访问 DOM);
Threads.js 是基于 Web Workers 的封装库,简化了多线程编程 (如支持 Promise API、自动管理 Worker 池、支持传递函数),本质还是依赖浏览器的 Web Workers 能力,并未突破其核心限制;
API 篇:WebSocket
上一篇