JS中webworker的作用与实操示例详解
作者:搞前端的小王
为什么需要webworker
JavaScript 需要引入 Web Worker 的核心原因源自其单线程模型的局限性。以下是关键原因及技术价值分析:
一、突破单线程阻塞瓶颈
1.JS 单线程的本质
JavaScript 设计之初基于单线程模型,主线程需同时处理 UI 渲染、事件响应、逻辑计算等任务。若执行耗时操作(如大数据计算),会导致页面卡顿甚至无响应,用户体验急剧下降。Web 2.Worker 的解决方案
创建独立的后台线程运行脚本,将复杂任务剥离主线程,避免阻塞关键渲染流程
二、释放多核 CPU 性能潜力
1.并行计算能力
Web Worker 允许创建多个子线程,将任务分片并行处理(如图像滤镜、科学计算),充分利用多核 CPU 硬件资源,显著提升处理效率
2.资源分配优化
主线程专注 UI 交互与渲染,Worker 线程处理计算密集型任务,实现计算资源合理分配
三、增强应用稳定性与体验
1.崩溃隔离机制
Worker 线程运行在独立环境中,即使子线程崩溃或报错,也不会影响主线程及其他 Worker 的稳定性
2.流畅的用户交互保障
例如实时金融图表渲染场景,主线程仅负责接收结果并渲染,确保用户操作无延迟
webworker的原理
Web Worker 是 JavaScript 实现多线程的核心机制,其原理涉及底层线程模型、通信机制和环境隔离等多个关键技术点。
核心工作机制详解
1. 独立线程模型
- Web Worker 在独立操作系统线程中运行(非主线程)
- 浏览器为每个 Worker 分配:
- 独立内存堆栈空间
- 专属 JavaScript 执行环境
- 隔离的全局上下文 (
self
代替window
)
2. 线程间通信机制
// 主线程
const worker = new Worker('worker.js'); worker.postMessage(data); // 发送数据 // Worker 线程 (worker.js) self.onmessage = (e) => { const result = process(e.data); self.postMessage(result); // 返回结果 }
3. 事件循环隔离
特性 | 主线程 | Web Worker |
---|---|---|
事件循环 | UI渲染+JS执行 | 纯JS执行环境 |
阻塞影响 | 导致页面冻结 | 仅影响自身线程 |
任务优先级 | 高(UI响应优先) | 低(计算任务优先) |
4. 资源访问限制
Web Worker 无法访问:
- DOM API (
document
,window
) - 父页面变量/函数
- 部分BOM API (
alert
,localStorage
)
但可以访问:
XMLHttpRequest
/Fetch
setTimeout
/setInterval
WebSockets
IndexedDB
5. 线程生命周期管理
// 创建Worker const worker = new Worker('worker.js'); // 终止Worker (立即停止) worker.terminate(); // Worker内部自终止 self.close();
- 创建开销:约5-20ms (取决于浏览器)
- 内存独立:Worker崩溃不影响主线程
- 自动回收:页面关闭时自动销毁
底层实现差异(浏览器引擎)
浏览器引擎 | 线程模型实现 | 特点 |
---|---|---|
Chromium | Blink渲染引擎 + V8隔离实例 | 每个Worker独立V8实例 |
Firefox | SpiderMonkey独立上下文 | 共享JIT编译器但隔离堆栈 |
Safari | JavaScriptCore独立虚拟机 | 精细内存管理 |
性能优化要点
- 线程复用:避免频繁创建/销毁 (线程池模式)
- 批量通信:合并多次消息传递
- 高效数据结构:优先使用TypedArray
- 负载均衡:根据CPU核心数动态分配任务
- 超时控制:防止僵尸线程
Web Worker 通过线程隔离和高效通信机制,在保持JavaScript单线程编程模型的同时,实现了真正的并行计算能力,成为现代Web应用性能优化的关键基础设施。
webworker典型应用场景
场景类型 | 实例 | 技术价值 |
---|---|---|
大数据处理 | 实时日志分析/金融统计 | 避免遍历海量数据阻塞 UI27 |
音视频处理 | 4K 视频解码/Canvas 滤镜 | 分离计算与渲染流程17 |
高频交互优化 | 游戏物理引擎/传感器数据处理 | 减少主线程压力25 |
预加载与缓存 | SPA 资源预载入 | 结合 Service Worker 提升加载速度 |
webworker实操
写在前面:为什么主线程执行大量的同步运算会导致页面卡顿?
浏览器默认会有一个刷新频率,这和个人电脑的默认刷新率有关,一般默认为60HZ,也就是浏览器页面会一秒钟刷新60次,计算下来差不多每16ms就要刷新一次,才能保证页面上所有的样式和布局以及DOM节点信息是最新的,如果主线程此时运行大量运算,超过16ms,就会造成渲染阻塞。这就是需要用webworker分摊主线程大量运算的原因,至于每个webworker运行多久不需要我们担心,因为它在其他线程上,每个worker都是一个新线程,它不在js主线程上,它的运行不会阻塞页面渲染。
细节点补充:
1.如何查看自己电脑上的实际cpu核数与逻辑处理器核数
任务管理器>性能面板>内核数与逻辑处理器数(一般逻辑处理器数会比真实内核数要多)
2.电脑屏幕刷新率查看:
设置>系统>屏幕>高级显示器设置>刷新率
因为webworker属于web API,所以必须要运行在浏览器环境下,需要通过live-server插件运行示例html或者将相关代码放置在一个vue或react模板工程中执行
index.html内容
<!doctype html> <html lang="en"> <body> <div id="app"></div> <!-- 启用webworker效果 --> <script src="fiberMain.js"></script> <!-- 不启用webworker效果 --> <!-- <script src="fiberWithout.js"></script> --> </body> </html>
fiberMain.js
// 主线程代码,引用fiberWorker.js作为worker线程执行代码,实现大量cpu密集型运算从主线程中拆分至worker线程中。
const startTime = Date.now(); // 动态启用本机电脑上的cpu逻辑处理器核数并发执行多个线程 const workCount = navigator.hardwareConcurrency || 4; const fiberArr = Array(50).fill().map((_, index)=> 30 + index); // 创建多个worker示例 const workers = Array(workCount).fill().map(() => new Worker('./fiberWorker.js')) const chunkSize = Math.ceil(fiberArr.length / workCount); // 任务切片分配给各个worker workers.forEach((worker, i) => { const start = i * chunkSize; const chunk = fiberArr.slice(start, start + chunkSize); worker.postMessage({chunk, id : i}); worker.onmessage = (e) => { // 每个worker运算结束后worker的单独耗时 console.log('每一个worker计算结果结束完成后经过的毫秒数::', (Date.now() - startTime)); } worker.onerror = function (err) { console.error(err) } }) // 主线程代码执行到这里的耗时 console.log('主线程同步代码执行完成后的耗时毫秒数:::', (Date.now() - startTime))
fiberWorker.js worker线程所要执行的逻辑代码,需要和fibermain.js搭配使用
// worker计算逻辑,斐波那契递归数列计算 self.onmessage = (e) => { const res = e.data.chunk.map(n => fibonacci(n)) self.postMessage({res, id: e.data.id}) } // 斐波那契计算函数(模拟耗时操作) function fibonacci(n) { if (n <= 1) return n; // 基线条件:fib(0)=0, fib(1)=1 return fibonacci(n - 1) + fibonacci(n - 2); // 递归调用 }
fiberWithout.js
// 无worker线程,大量的递归运算直接运行在主线程中的写法,主线程同步执行大量运算导致耗时很长,大概2000ms,会严重阻塞每16ms就要执行一次的页面渲染,导致页面和用户交互异常卡顿。
// 不用worker,主线程单线程计算 const startTime = Date.now(); const fiberArr = Array(22).fill().map((_, index)=> 20 + index); function fibonacci(n) { return n <= 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); } const res = fiberArr.map(n => fibonacci(n)); console.log('同步计算的最终结果::', res); console.log('主线程同步计算的耗时毫秒数:::', (Date.now() - startTime));
fiberWithout.js 代码在主线程上运行相同的斐波那契数列计算逻辑耗时2013ms
fiberMain.js 配合 fiberWorker.js 在主线程上运行相关代码大大缩短同步代码等待执行时间,因为把大量的cpu密集型(递归回调)运算放置在了多个worker中运算,所以主线程的同步代码执行很快,耗时2ms,但是这并不意味这worker内单个线程中的运算很快速,实际每个worker运算耗时加着线程间的通信,单个worker最长执行总耗时2064ms。
为什么要启用worker,因为js主线程是单线程,js执行与页面渲染全在主线程上执行,js同步代码执行过长会导致渲染阻塞,导致页面卡顿,如果把上面的运算逻辑放在主线程中执行(也就是fiberWithout.js这种写法),那么会导致页面有2000多ms的渲染阻塞,因为js主线程需要每16ms就要运行一次页面渲染逻辑。所以会导致页面卡顿,造成页面渲染不及时以及用户交互得不到及时响应。
把大量的运算分散到多个worker中,相当于把主线程的大量运算分摊到了其他线程中,缩减主线程同步代码至2ms,不会造成页面卡顿。至于每个worker运行2000ms那不是我们关心的重点,worker就是用来分摊主线程的运算负担的,防止页面卡顿。
webworker技术限制与注意事项
1.无法操作 DOM
Worker 线程无法访问 window
、document
等对象,需通过 postMessage
与主线程通信
2.通信成本控制
频繁跨线程传递大数据(如图像二进制流)可能抵消性能优势,需采用 Transferable Objects
优化
// 高效传递 ArrayBuffer worker.postMessage(buffer, [buffer]);
总结
到此这篇关于JS中webworker的作用与实操的文章就介绍到这了,更多相关JS webworker详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!