实习摸鱼学Web Worker:算1亿个商品价格,终于不卡了!

0 阅读8分钟

家人们谁懂啊!实习做项目时踩了个大坑😭 项目里需要处理后端返回的Excel大数据,或是批量商品数据,一旦对这些海量数据做计算处理,页面直接卡死,转圈动画停在原地,用户体验拉满(负的)。直到解锁Web Worker,才发现原来“主线程解放”这么香!今天就用最通俗的话+实操例子,跟大家聊聊Web Worker是什么、怎么用,以及它到底能帮我们优化多少性能,解决大数据处理时的卡顿难题。

先搞懂:Web Worker 到底是个啥?(人话版)

我们写的前端代码,默认都是在“主线程”里运行的——主线程就像一个单线程打工人,又要负责渲染页面(比如动画、按钮点击),又要负责做复杂计算(比如算1亿个商品价格)。

这就好比一个人又要做饭,又要打扫卫生,又要哄孩子,忙不过来的时候,做饭就会糊,卫生也搞不干净(对应页面卡顿、动画卡死)。

而Web Worker,就是我们给主线程找的“兼职工人”——它专门负责干“复杂计算”这种费力不讨好的活,不占用主线程的时间,主线程该渲染动画、响应用户操作,一点不耽误。两者各司其职,效率直接翻倍!

核心重点(记牢不踩坑):Web Worker 不能操作DOM(比如修改页面元素),也不能访问window对象,它只负责“计算”,算完之后把结果交给主线程,剩下的渲染工作还是主线程来做。

实操对比:算1亿个商品价格,两种方式天差地别

不多说,直接上案例——我们模拟一个场景:有1亿个商品,每个商品的基础价格是随机数,需要计算每个商品的“折后价”(基础价×0.8),同时页面有一个转圈动画,观察动画是否卡顿,就能直观看到性能差异。

案例分两种方式:① 默认渲染(主线程计算);② Web Worker(兼职线程计算),再加上“转圈检测”,一眼看出区别。

先搭基础页面:转圈动画+两个计算按钮

首先写一个简单的页面,有一个旋转的圆形(用来检测是否卡顿),还有两个按钮,分别对应“主线程计算”和“Web Worker计算”。代码很简单,直接复制就能用:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Web Worker 性能测试(1亿商品价格计算)</title>
  <style>
    /* 转圈动画:用来检测是否卡顿 */
    .circle {
      width: 50px;
      height: 50px;
      border: 5px solid #eee;
      border-top-color: #42b983;
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin: 20px 0;
    }
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    button {
      padding: 10px 20px;
      margin-right: 10px;
      cursor: pointer;
    }
    .result {
      margin-top: 20px;
      color: #333;
    }
  </style>
</head>
<body>
  <h1>1亿商品价格计算测试</h1>
  <!-- 转圈动画:卡顿与否一眼看穿 -->
  <div class="circle"></div>
  <button id="normalBtn">主线程计算(会卡顿)</button>
  <button id="workerBtn">Web Worker计算(不卡顿)</button>
  <div class="result" id="result">点击按钮开始计算...</div>

  <script>
    // 后续代码分两种方式添加
  </script>
</body>
</html>

方式1:默认渲染(主线程计算)—— 卡顿到怀疑人生

直接在主线程里写计算逻辑,计算1亿个商品的折后价。代码如下,添加到上面的script标签里:

const normalBtn = document.getElementById('normalBtn');
const result = document.getElementById('result');

// 主线程计算:1亿个商品价格(折后价=基础价×0.8)
normalBtn.addEventListener('click', () => {
  result.textContent = '计算中...(主线程忙碌,看看转圈动画卡不卡)';
  const start = Date.now();
  
  // 模拟1亿个商品,每个商品基础价是0-100的随机数
  const goodsCount = 100000000;
  let totalPrice = 0; // 可选:统计总折后价,更直观
  for (let i = 0; i < goodsCount; i++) {
    const basePrice = Math.random() * 100;
    const discountPrice = basePrice * 0.8;
    totalPrice += discountPrice;
  }
  
  const end = Date.now();
  result.textContent = `主线程计算完成!耗时:${(end - start) / 1000}秒,总折后价:${totalPrice.toFixed(2)}元`;
});

✨ 测试效果:点击按钮后,你会发现——转圈动画直接停住了!页面无法点击、无法滚动,直到计算完成(大概几秒到十几秒,看电脑配置),动画才会恢复旋转。

❌ 问题核心:1亿次循环计算属于“ heavy task ”(重任务),主线程被计算占用,根本没时间处理动画渲染和用户交互,导致页面卡顿。

方式2:Web Worker 计算—— 动画丝滑到飞起

这时候就该Web Worker登场了!我们把“计算1亿个商品价格”的逻辑,交给Web Worker来做,主线程只负责触发计算、接收结果和渲染动画。

步骤分两步:① 创建Worker文件(负责计算);② 主线程调用Worker,接收结果。

第一步:创建Worker文件(worker.js)

新建一个名为worker.js的文件,里面只写计算逻辑,不用管DOM和动画:

// worker.js:专门负责计算,不占用主线程
self.onmessage = (e) => {
  // 接收主线程传来的指令(这里我们不需要传参数,直接计算)
  const goodsCount = 100000000;
  let totalPrice = 0;
  const start = Date.now();
  
  // 同样的1亿次计算逻辑
  for (let i = 0; i < goodsCount; i++) {
    const basePrice = Math.random() * 100;
    const discountPrice = basePrice * 0.8;
    totalPrice += discountPrice;
  }
  
  const end = Date.now();
  // 计算完成,把结果发送给主线程
  self.postMessage({
    time: (end - start) / 1000,
    totalPrice: totalPrice.toFixed(2)
  });
};

// 监听主线程的终止指令(可选,防止内存泄漏)
self.onmessage = (e) => {
  if (e.data === 'terminate') {
    self.close();
  }
};

第二步:主线程调用Worker(修改原页面的script标签)

把主线程的计算逻辑删掉,换成调用Worker的代码,让Worker去干活:

const normalBtn = document.getElementById('normalBtn');
const workerBtn = document.getElementById('workerBtn');
const result = document.getElementById('result');

// 1. 主线程计算(保留,用于对比)
normalBtn.addEventListener('click', () => {
  result.textContent = '计算中...(主线程忙碌,看看转圈动画卡不卡)';
  const start = Date.now();
  const goodsCount = 100000000;
  let totalPrice = 0;
  for (let i = 0; i < goodsCount; i++) {
    const basePrice = Math.random() * 100;
    const discountPrice = basePrice * 0.8;
    totalPrice += discountPrice;
  }
  const end = Date.now();
  result.textContent = `主线程计算完成!耗时:${(end - start) / 1000}秒,总折后价:${totalPrice.toFixed(2)}元`;
});

// 2. Web Worker计算(重点!)
let worker; // 声明Worker实例,避免重复创建
workerBtn.addEventListener('click', () => {
  // 避免重复创建Worker(优化内存)
  if (worker) worker.terminate();
  worker = new Worker('worker.js');
  
  result.textContent = '计算中...(Web Worker干活,看看转圈动画丝滑不)';
  
  // 接收Worker发送的结果
  worker.onmessage = (e) => {
    result.textContent = `Web Worker计算完成!耗时:${e.data.time}秒,总折后价:${e.data.totalPrice}元`;
    // 计算完成,终止Worker,释放资源
    worker.terminate();
  };
  
  // 发送指令给Worker,开始计算(这里不需要传参数,传个空对象也可以)
  worker.postMessage({});
});

// 页面关闭时,终止Worker(防止内存泄漏)
window.addEventListener('beforeunload', () => {
  if (worker) worker.terminate();
});

✨ 测试效果:点击“Web Worker计算”按钮后,转圈动画依旧丝滑旋转!页面可以正常点击、滚动,完全不卡顿,直到计算完成,主线程接收结果并显示——这就是Web Worker的魅力!

注意一个小细节:首次加载/首次调用Worker时,可能会有轻微卡顿(几十毫秒),因为浏览器需要创建Worker线程、打包数据并传递给Worker,这是正常现象,后续调用会更流畅。

核心总结:Web Worker 性能优化的关键逻辑

通过上面的例子,我们能清晰get到Web Worker的核心价值,以及它和默认渲染(主线程计算)的区别,用表格总结更直观:

对比维度默认渲染(主线程计算)Web Worker(分线程计算)
主线程占用完全占用,无法处理其他任务不占用,主线程可正常渲染、交互
页面动画卡顿、停止丝滑、正常运行
首次加载无额外卡顿(直接占用主线程)轻微卡顿(创建线程、打包数据)
适用场景简单计算、轻量任务大数据运算、复杂逻辑(如统计、加密、图表渲染)

实习感悟:什么时候该用Web Worker?

作为实习小白,以前总觉得“性能优化”是很遥远的事,直到用Web Worker解决了“计算卡顿”的问题,才发现优化就在细节里。

分享几个适合用Web Worker的场景(前端日常开发高频):

  • 大数据统计/计算(比如本文的商品价格计算、用户数据统计);
  • 复杂的逻辑处理(比如数据加密、格式转换、大文件解析);
  • 图表渲染(比如大数据量的折线图、柱状图,避免渲染时卡顿);
  • 任何会占用主线程超过100ms的任务(超过100ms用户就会感觉到卡顿)。

最后提醒一个坑:不要滥用Web Worker!创建Worker会占用一定的内存和资源,如果是简单的计算(比如计算10个商品价格),用主线程反而更高效,没必要多此一举。

结尾:Web Worker 核心要点速记(怕忘就存下)

  1. Web Worker 是主线程的“兼职”,专门干重计算,不占主线程;
  2. 不能操作DOM、不能访问window,只负责计算,结果传给主线程;
  3. 首次调用可能轻微卡顿(创建线程+数据打包),后续丝滑;
  4. 计算完成后要终止Worker,避免内存泄漏;
  5. 适合重任务,轻任务不用多此一举。

今天的Web Worker学习就到这里啦!从“卡顿到崩溃”到“丝滑运行”,一个小小的Worker就能解决项目中Excel大数据、批量商品数据处理的卡顿难题,这就是前端性能优化的意义吧~ 实习做项目的过程中,也发现了更进阶的方向——当数据量达到亿级以上、计算逻辑更复杂时,Web Worker的性能也会遇到瓶颈,这时候就可以考虑Wasm(WebAssembly)来进一步提升运算效率。关于Wasm的实操和对比,咱们留到后续文章详细拆解,感兴趣的小伙伴可以持续关注哦!😆