一、为什么 Web Worker 能实现 "多线程"?
浏览器的 JS 引擎(如 V8)本身是单线程的,但浏览器是多进程 / 多线程架构。Web Worker 的本质是 浏览器为 JS 提供的 "额外线程池" ,核心原理:
- 线程隔离:Worker 线程与主线程是完全隔离的内存空间(不共享堆内存),通过 "消息队列" 通信(数据传输基于结构化克隆算法,深拷贝)。
- 阻塞无关性:Worker 线程的阻塞(如死循环)不会影响主线程,但会导致自身无法响应消息。
- 资源限制:浏览器对 Worker 数量有上限(通常同源下不超过 20 个),且单个 Worker 占用的内存有限制(避免滥用系统资源)。
关键区别 :与 Node.js 的 child_process 不同,Web Worker 无法共享内存(需通过 SharedArrayBuffer 实现有限共享,见下文),且受浏览器安全策略(如跨域限制)约束。
总结: webwork是通过js的方式唤起浏览器的内置api使用,辅助前端计算的一种方式,就像fetch、ajaix那样唤起浏览器的接口查询一样。
多线程四大API
Web Worker 是前端 "多线程" 的基础规范,但浏览器针对不同场景设计了 4 种独立的 Worker 类型------ 它们共享 "线程隔离" 的核心思想(避免阻塞主线程),但定位、能力、使用场景完全不同,均为 W3C 标准定义的原生 API(无需第三方库),底层由浏览器独立实现(无依赖关系)。
浏览器 4 种核心 Worker 类型对比表
| Worker 类型 | 核心定位 | 底层依赖 | 典型场景 |
|---|---|---|---|
| Web Worker | 主线程的 "计算助手",处理耗时计算 | 浏览器线程池 | 大数据排序、加密解密、复杂算法计算 |
| Shared Worker | 多页面共享的 "后台协调者",跨页面通信 | 浏览器共享线程 | 多标签页登录状态同步、跨页数据协同 |
| Service Worker | 页面离线的 "代理服务器",拦截请求 + 缓存 | 浏览器后台线程 | 离线应用、请求拦截优化、浏览器推送通知 |
| Worklet | 渲染管线的 "实时处理器",介入渲染流程 | 浏览器渲染线程 | CSS 物理动画、音频降噪、Canvas 渲染优化 |
二、分类
1. Web Worker(计算型:解决主线程阻塞)
核心能力:
- 独立于主线程的 "计算线程",仅与创建它的主线程通信(一对一);
- 生命周期与页面绑定(页面关闭则 Worker 销毁);
- 不阻塞 UI,专门处理耗时计算(避免页面卡顿)。
示例:10 万条数据排序
javascript
// 主线程(main.js):发起计算请求,接收结果
if (window.Worker) {
// 1. 创建 Worker 实例
const sortWorker = new Worker('sort-worker.js');
// 2. 生成 10 万条随机大数据(模拟复杂计算场景)
const bigData = Array.from({ length: 100000 }, () => Math.random() * 100000);
// 3. 发送数据给 Worker,触发计算
sortWorker.postMessage(bigData);
console.log('主线程:已发送数据,等待排序结果...');
// 4. 接收 Worker 返回的计算结果
sortWorker.onmessage = (e) => {
console.log('主线程:排序完成,前 10 条数据:', e.data.slice(0, 10));
sortWorker.terminate(); // 计算完成,销毁 Worker(避免内存泄漏)
};
// 5. 监听 Worker 错误(如代码报错)
sortWorker.onerror = (err) => {
console.error(`Worker 错误:${err.message}(行号:${err.lineno})`);
sortWorker.terminate();
};
} else {
console.error('当前浏览器不支持 Web Worker');
}
// Worker 线程(sort-worker.js):执行耗时计算
self.onmessage = (e) => {
const data = e.data;
console.log('Worker 线程:开始排序 10 万条数据...');
// 耗时计算(示例用内置排序,实际可替换为快排、归并等复杂算法)
const sortedData = data.sort((a, b) => a - b);
// 向主线程返回结果
self.postMessage(sortedData);
self.close(); // Worker 主动关闭,释放资源
};
运行效果:
主线程可正常处理用户交互(点击、滚动),排序在后台线程执行,页面无卡顿;计算完成后自动销毁 Worker,无内存泄漏。
2. Shared Worker(协同型:多页面数据同步)
核心能力:
- 同源多页面可共享同一个 Worker 实例(突破 "单页面私有" 限制);
- 通过 port(通信端口)实现多页面与 Worker 的双向通信;
- 适合跨页面状态同步(无需重复请求接口)。
示例:多标签页登录状态同步
javascript
// 主线程 - 页面 A(login.html):登录后同步状态
if (window.SharedWorker) {
// 1. 创建 Shared Worker 实例
const sharedWorker = new SharedWorker('sync-worker.js');
// 2. 激活通信端口(必须调用 start())
sharedWorker.port.start();
// 3. 模拟登录按钮点击,发送登录状态给 Worker
document.getElementById('login-btn').addEventListener('click', () => {
const userInfo = { username: 'admin', isLogin: true };
sharedWorker.port.postMessage({ type: 'LOGIN', data: userInfo });
console.log('页面 A:已发送登录状态');
});
// 4. 接收 Worker 广播的消息(如其他页面同步的状态)
sharedWorker.port.onmessage = (e) => {
console.log('页面 A 收到消息:', e.data);
};
}
// 主线程 - 页面 B(index.html):实时获取登录状态
if (window.SharedWorker) {
const sharedWorker = new SharedWorker('sync-worker.js');
sharedWorker.port.start();
// 1. 向 Worker 请求当前登录状态
sharedWorker.port.postMessage({ type: 'QUERY_STATUS' });
// 2. 接收状态(页面 A 登录后,页面 B 实时更新)
sharedWorker.port.onmessage = (e) => {
if (e.data.type === 'LOGIN_STATUS') {
document.getElementById('status').textContent = e.data.isLogin
? `已登录:${e.data.username}`
: '未登录';
}
};
}
// Shared Worker 线程(sync-worker.js):维护全局状态,广播消息
const connections = []; // 存储所有连接的页面端口
let globalState = { isLogin: false, username: '' }; // 全局共享状态
// 监听页面连接(新页面打开时触发)
self.onconnect = (e) => {
const port = e.ports[0];
connections.push(port); // 记录新连接的页面
port.start();
// 处理页面发送的消息
port.onmessage = (msg) => {
switch (msg.data.type) {
case 'LOGIN':
// 更新全局状态
globalState = msg.data.data;
// 广播给所有连接的页面(同步状态到页面 A、B...)
connections.forEach(p => p.postMessage({
type: 'LOGIN_STATUS',
...globalState
}));
break;
case 'QUERY_STATUS':
// 单独响应某个页面的状态查询
port.postMessage({ type: 'LOGIN_STATUS', ...globalState });
break;
}
};
// 页面关闭时,移除端口(避免内存泄漏)
port.onclose = () => {
const index = connections.indexOf(port);
if (index !== -1) connections.splice(index, 1);
};
};
运行效果:
页面 A 点击 "登录" 后,页面 B 无需刷新,实时显示 "已登录:admin";多标签页共享同一登录状态,无需重复调用登录接口。
3. Service Worker(网络型:离线缓存 + 请求拦截)
核心能力:
- 完全独立于页面,运行在浏览器后台(页面关闭后仍可活动);
- 拦截所有网络请求,可自定义缓存策略(实现离线访问);
- 生命周期包含 "安装→激活→运行",需手动管理缓存版本。
示例:离线缓存静态资源 + API 请求
javascript
// 主线程(main.js):注册 Service Worker,触发离线逻辑
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
// 1. 注册 Service Worker(脚本需在根目录,确保作用域覆盖所有页面)
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker 注册成功,作用域:', registration.scope);
// 2. 测试离线请求(首次联网缓存,后续断网可访问)
fetch('/api/data').then(res => res.json()).then(data => {
console.log('API 请求结果:', data);
});
} catch (err) {
console.error('Service Worker 注册失败:', err);
}
});
}
// Service Worker 线程(sw.js):缓存+请求拦截逻辑
const CACHE_NAME = 'offline-cache-v1'; // 缓存版本(更新时修改版本号)
// 需要缓存的资源列表(静态资源+API接口)
const CACHE_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/api/data' // 需缓存的 API 接口
];
// 1. 安装阶段:缓存静态资源(仅首次注册/版本更新时触发)
self.addEventListener('install', (event) => {
// 等待缓存完成后,再进入激活阶段
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(CACHE_ASSETS)) // 批量缓存资源
.then(() => self.skipWaiting()) // 跳过等待,立即激活新 Worker(替换旧版本)
);
});
// 2. 激活阶段:清理旧缓存(避免缓存膨胀)
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(cacheNames => {
// 删除非当前版本的缓存
return Promise.all(
cacheNames.filter(name => name !== CACHE_NAME).map(name => caches.delete(name))
);
}).then(() => self.clients.claim()) // 强制所有打开的页面使用新 Worker
);
});
// 3. 拦截请求:优先从缓存返回,无缓存则请求网络
self.addEventListener('fetch', (event) => {
// 仅拦截同源的 GET 请求(避免跨域资源和非幂等请求)
if (event.request.method === 'GET' && event.request.mode === 'same-origin') {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 缓存命中:直接返回缓存资源
if (cachedResponse) {
console.log('从缓存返回:', event.request.url);
return cachedResponse;
}
// 缓存未命中:发起网络请求,并缓存新结果
return fetch(event.request).then(networkResponse => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone()); // 缓存新请求
});
return networkResponse;
});
})
);
}
});
运行效果:
- 首次联网时,自动缓存 index.html、styles.css 和 /api/data;
- 断网后刷新页面,仍能正常加载页面和 API 数据(从缓存读取);
- 缓存版本更新时,自动清理旧缓存,避免资源冗余。
4. Worklet(实时型:介入渲染流程)
核心能力:
- 嵌入浏览器渲染管线(如 CSS 引擎线程、音频线程),低延迟(<1ms);
- 直接干预渲染过程(动画、绘制、音频处理),弥补 Web Worker 通信延迟的缺陷;
- 细分类型:CSSWorklet(动画)、AudioWorklet(音频)、PaintWorklet(绘制)。
示例:CSSWorklet 实现物理弹性动画
typescript
// 主线程(main.js):注册 Worklet,关联动画
if ('CSSWorklet' in window) {
try {
// 注册自定义 Worklet(加载动画逻辑脚本)
await CSSWorklet.addModule('bounce-worklet.js');
console.log('CSSWorklet 注册成功');
} catch (err) {
console.error('CSSWorklet 注册失败:', err);
}
}
// HTML 结构:动画元素
/*
<div class="box">弹性动画方块</div>
*/
// CSS 样式:绑定 Worklet 动画
/*
.box {
width: 100px;
height: 100px;
background: red;
/* 使用 Worklet 定义的动画(名称与 Worklet 中注册一致) */
animation: bounce 2s infinite;
}
/* 定义动画进度(from→to 对应 Worklet 中的 0→1) */
@keyframes bounce {
from { transform: translateY(0); }
to { transform: translateY(300px); }
}
*/
// CSSWorklet 线程(bounce-worklet.js):自定义动画逻辑
class BounceWorklet {
// 每一帧的计算(嵌入渲染管线,实时执行)
process(inputs, outputs, parameters) {
const [t] = inputs; // 动画进度(0~1,from→to)
const [output] = outputs; // 输出结果(最终的 CSS 样式值)
// 物理弹性公式(模拟重力+反弹效果,非匀速动画)
const bounce = Math.sin(t * Math.PI) * Math.exp(-t * 0.5);
// 输出 transform 样式(控制方块位置)
output.value = `translateY(${300 * (1 - bounce)}px)`;
}
}
// 注册 Worklet 动画(名称需与 CSS 中的 @keyframes 名称一致)
registerAnimator('bounce', BounceWorklet);
运行效果:
红色方块以 "物理弹性轨迹" 上下运动(类似小球落地反弹),动画流畅无卡顿;Worklet 运行在渲染线程,延迟极低,避免 Web Worker 通信导致的动画掉帧。
三、混合使用:多 Worker 协同解决复杂场景
场景需求:离线数据分析 Dashboard
需要同时满足 4 个核心需求:
- 离线访问(断网后仍可查看历史数据);
- 多标签页同步分析结果(无需重复解析);
- 后台解析 10 万条 CSV 数据(不阻塞页面);
- 实时渲染流畅图表(动画无掉帧)。
完整实现代码(整合 4 种 Worker)
javascript
// 主线程(dashboard.js):整合所有 Worker,协调流程
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
// 1. 第一步:注册 Service Worker(离线缓存)
const swRegistration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker 注册成功');
// 2. 第二步:创建 Shared Worker(多页面同步)
const sharedWorker = new SharedWorker('sync-worker.js');
sharedWorker.port.start();
// 3. 第三步:创建 Web Worker(CSV 数据解析)
const csvWorker = new Worker('csv-worker.js');
// 4. 第四步:注册 CSSWorklet(图表动画优化)
if ('CSSWorklet' in window) {
await CSSWorklet.addModule('chart-worklet.js');
console.log('CSSWorklet 注册成功');
}
// 5. 页面元素:文件上传、图表容器、状态文本
const fileInput = document.getElementById('file-upload');
const chartContainer = document.getElementById('chart-container');
const statusText = document.getElementById('status');
// 6. 监听文件上传:触发 CSV 解析
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file && file.name.endsWith('.csv')) {
statusText.textContent = '正在解析 CSV 文件...';
csvWorker.postMessage(file); // 发送文件给 Web Worker
} else {
statusText.textContent = '请上传 CSV 格式文件!';
}
});
// 7. 接收 Web Worker 解析结果:渲染+同步+缓存
csvWorker.onmessage = (e) => {
const analysisResult = e.data; // 解析结果(数据数组+统计信息)
// 7.1 渲染图表(用 CSSWorklet 优化动画)
renderChart(analysisResult, chartContainer);
// 7.2 同步结果到其他标签页(通过 Shared Worker)
sharedWorker.port.postMessage({
type: 'ANALYSIS_RESULT',
data: analysisResult
});
// 7.3 缓存结果到 Service Worker(支持离线访问)
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'CACHE_RESULT',
key: 'last-csv-analysis',
data: analysisResult
});
}
// 7.4 更新状态文本
statusText.textContent = `解析完成:共 ${analysisResult.totalCount} 条</doubaocanvas>
四、关于self
arduino
// 1. 来源:Worker 线程的内置全局对象,浏览器自动注入,无需定义
// 2. 作用:等同于主线程的 window,是 Worker 线程访问自身能力的入口
// 3. 隔离性:不同 Worker 的 self 是独立实例,互不干扰(如 Web Worker 的 self ≠ Service Worker 的 self)
// 4. 核心能力:接收/发送消息(onmessage/postMessage)、管理生命周期(close)、调用 Worker 专属 API
Worker 中 self 的简单代码示例
javascript
// Worker 线程(calc-worker.js)
// self 指向当前 Web Worker 实例,仅用于计算相关逻辑
self.onmessage = (e) => {
const { num1, num2 } = e.data;
const sum = num1 + num2; // 简单计算:两数相加
self.postMessage(sum); // 用 self 向主线程返回结果
self.close(); // 用 self 主动关闭 Worker,释放资源
};
// 主线程(main.js)- 配合使用
const calcWorker = new Worker('calc-worker.js');
calcWorker.postMessage({ num1: 10, num2: 20 }); // 发数据给 Worker
calcWorker.onmessage = (e) => {
console.log('计算结果:', e.data); // 输出 30
};
注意:
lua
// 注意 1:Worker 中不能用 window,必须用 self(因 Worker 无窗口概念)
// 注意 2:self 无法访问 DOM(如 document、body),仅能处理非 UI 逻辑
// 注意 3:简单场景可省略 self(如 onmessage = () => {} 等同于 self.onmessage = () => {}),但复杂场景建议保留,增强可读性
五、生产环境使用的平衡点
1. 错误处理与健壮性
-
Worker 内部错误不会冒泡到主线程,需单独监听:
javascript// 主线程 worker.onerror = (err) => { console.error(`Worker错误:${err.message}`); worker.terminate(); // 出错后销毁Worker,避免内存泄漏 }; // Worker线程 self.onerror = (err) => { self.postMessage({ type: 'ERROR', message: err.message }); self.close(); // 主动关闭 }; -
网络错误:Worker 脚本加载失败(404)时,主线程会触发
error事件,需捕获并降级处理。
2. 内存管理与资源回收
- 避免创建过多 Worker:每个 Worker 都是独立线程,占用内存和 CPU,建议通过 "Worker 池" 复用(如用
p-queue管理 Worker 实例)。 - 及时销毁无用 Worker:
worker.terminate()(主线程主动销毁,立即终止)或self.close()(Worker 主动关闭,清理后终止)。 - 警惕内存泄漏:Worker 中若持有
setInterval、未关闭的fetch请求等,即使调用terminate也可能导致内存泄漏,需先清理资源。
3. 跨域与安全策略
- Worker 脚本必须与主线程同源(协议、域名、端口一致),若需加载跨域脚本,需通过
importScripts加载且服务器允许跨域(Access-Control-Allow-Origin)。 - 禁止访问
file://协议下的脚本(浏览器安全限制),本地开发需用http-server启动服务。 - 敏感操作限制:Worker 中无法使用
localStorage(部分浏览器支持,但规范不推荐),建议用IndexedDB存储大量数据(异步 API,不阻塞)。
4. 兼容性处理与降级方案
-
浏览器支持:IE 完全不支持,Edge 12+、Chrome 4+、Firefox 3.5+ 支持基本特性,
SharedArrayBuffer和SharedWorker兼容性较差。 -
降级逻辑:
scssif (window.Worker) { // 使用Worker } else { // 降级到主线程执行(给用户提示"当前浏览器可能卡顿") heavyTask(); }
六、性能陷阱
-
过度使用 Worker 导致性能反降 小数据计算(如几毫秒可完成的操作)用 Worker 反而会增加通信开销(序列化 + 消息传递),建议仅对 执行时间 > 50ms 的任务使用 Worker。
-
频繁通信导致主线程阻塞 若 Worker 与主线程高频次
postMessage(如每秒 hundreds 次),序列化数据会占用主线程资源,导致卡顿。解决方案:- 批量发送数据(累计一定量后再通信);
- 用
SharedArrayBuffer减少序列化开销。
-
Worker 中滥用同步 API Worker 虽然不阻塞 UI,但内部的同步操作(如
XMLHttpRequest的同步请求、大量同步循环)会阻塞自身线程,导致无法响应消息。建议优先使用异步 API(fetch、setImmediate)。 -
忽略线程优先级 浏览器会给 Worker 分配较低的线程优先级,若需 "近实时" 处理(如游戏帧计算),可能出现延迟。此时可考虑
requestIdleCallback结合 Worker,利用主线程空闲时间处理。