今天为大家分享一篇关于web worker的优质文章,让你了解一下如何通过Web Worker来解决前端处理大量数据运算时页面假死的问题。
以下是正文:
如何让前端拥有后端的计算能力,在算力紧缺的年代,扩展前端的业务边界! 
前言 页面中有十万条数据,对其进行复杂运算,需要多久呢?
表格4000行,25列,共十万条数据
运算包括 :总和、算术平均、加权平均、最大、最小、计数、样本标准差、样本方差、中位数、总体标准差、总体方差
table.jpg 
答案是: 35s 左右 
注:具体时间根据电脑配置会有所不同 
并且  这个时间段内,页面一直处于假死状态,对页面做任何操作都没有反应😭😭😭
boom.gif 
什么是假死? 
浏览器有GUI渲染线程与JS引擎线程,这两个线程是互斥的关系
当js有大量计算时,会造成 UI 阻塞,出现界面卡顿、掉帧等情况,严重时会出现页面卡死的情况,俗称假死 
致命bug 强行送测吧 
测试小姐姐:你的页面又死了!!致命bug 💥
绝望.jpg 
闯荡前端数十载,竟被提了个致命bug,颜面何在!🙈 
Performance分析假死期间的性能表现 如下图所示:此次计算总用时为35.45s 
重点从以下三个方面分析:
1)FPS 
FPS: 表示每秒传输帧数,是分析动画的一个主要性能指标,绿色的长度越长,用户体验越好;反之红色越长,说明卡顿严重
从图中看到FPS中有一条持续了35s的红线,说明这期间卡顿严重 
2)火焰图Main 
展开Main,红色倒三角的为Long Task ,执行时长50ms就属于长任务,会阻塞页面渲染
从图中看到计算过程的Long Task执行时间为35.45s, 是造成页面假死的原因 
3)Summary 统计汇总面板 
Scripting代码执行为35.9s 
performance8.png 
拿什么拯救你,我的页面 
召唤Web Worker,出来吧神龙 R-C (1).gif 
神龙,我想让页面的计算变快,并且不卡顿 
Web Worker了解一下: 
在HTML5的新规范中,实现了 Web Worker 来引入 js 的 “多线程” 技术, 可以让我们在页面主运行的 js 线程中,加载运行另外单独的一个或者多个 js 线程
一句话:Web Worker专门处理复杂计算的,从此让前端拥有后端的计算能力 
在Vue中 使用 Web Worker 1、安装worker-loader
npm install worker-loader2、编写worker.js
onmessage = function  (e) {let  sum = e.data;for  (let  i = 0; i < 200000; i++) {for  (let  i = 0; i < 10000; i++) {3、通过行内loader 引入 worker.js
import Worker from "worker-loader!./worker" 4、最终代码
<template>"makeWorker" >开始线程</button>type ="text" ></p>"worker-loader!./worker" ;export  default {makeWorker let  start = performance.now();let  worker = new Worker();"message" , (e) => {let  end = performance.now();let  durationTime = end - start;'计算结果:' , e.data);${durationTime}  毫秒`);计算过程中,在input框输入值,页面一直未发生卡顿
total.png 
对比试验 如果直接把下面这段代码直接丢到主线程中,计算过程中页面一直处于假死状态,input框无法输入
let  sum = 0;for  (let  i = 0; i < 200000; i++) {for  (let  i = 0; i < 10000; i++) {前戏差不多了,上硬菜 
开启多线程,并行计算 
回到要解决的问题,执行多种运算时,给每种运算开启单独的线程,线程计算完成后要及时关闭
多线程代码 
<template>"makeWorker" >开始线程</button>type ="text" ></p>"worker-loader!./worker" ;export  default {data let  arr = new Array(100000).fill(1).map(() => Math.random()* 10000);let  weightedList = new Array(100000).fill(1).map(() => Math.random()* 10000);let  calcList = [type : 'sum' , name: '总和' },type : 'average' , name: '算术平均' },type : 'weightedAverage' , name: '加权平均' },type : 'max' , name: '最大' },type : 'middleNum' , name: '中位数' },type : 'min' , name: '最小' },type : 'variance' , name: '样本方差' },type : 'popVariance' , name: '总体方差' },type : 'stdDeviation' , name: '样本标准差' },type : 'popStandardDeviation' , name: '总体标准差' }return  {makeWorker let  workerName = `worker${this.workerList.length} `;let  worker = new Worker();let  start = performance.now();type : item.type, weightedList: this.weightedList});"message" , (e) => {let  tastName = '' ;if (item.type === e.data.type) {let  end = performance.now();let  duration = end - start;${tastName} , 计算用时: ${duration}  毫秒`);clearWorker if  (this.workerList.length > 0) {${key} `].terminate && item[`worker${key} `].terminate(); // 终止所有线程beforeDestroy worker.js 
import { create, all } from 'mathjs' 'BigNumber' ,return  math.number(math.add(math.bignumber(arg1), math.bignumber(arg2)));return  math.number(math.subtract(math.bignumber(arg1), math.bignumber(arg2)));return  math.number(math.multiply(math.bignumber(arg1), math.bignumber(arg2)));return  math.number(math.divide(math.bignumber(arg1), math.bignumber(arg2)));return  Math.sqrt(popStandardDeviation(arr))let  s,for  (let  i = 0; i < len; i++) {for (let  i = 0; i < len; i++) {return  s;let  s,for  (let  i = 0; i < len; i++) {return  s;let  s,for  (let  i = 0; i < len; i++) {for (let  i = 0; i < len; i++) {return  s;if (arr.length%2 === 0){ //判断数字个数是奇数还是偶数return  numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶数个取中间两个数的平均数else {return  arr[(arr.length+1)/2-1];//奇数个取最中间那个数let  sum = 0, len = arr.length;for  (let  i = 0; i < len; i++) {return  sum;return  numberDivide(sum(arr), arr.length)let  max = arr[0]for  (let  i = 0; i < arr.length; i++) {if (max < arr[i]) {return  maxlet  min = arr[0]for  (let  i = 0; i < arr.length; i++) {if (min > arr[i]) {return  minlet  remove = ['' , ' ' , null , undefined, '-' ]; // 排除无效的数据return  arr.filter(item => !remove.includes(item)).lengthreturn  Math.sqrt(variance(arr))if  ((!num && num !== 0) || num == '-' ) return  '--' let  arr = (typeof num == 'string'  ? parseFloat(num) : num).toFixed(pointNum).split('.' )let  intNum = arr[0].replace(/\d{1,3}(?=(\d{3})+(.\d*)?$)/g,'$&,' )return  arr[1] === undefined ? intNum : `${intNum} .${arr[1]} `function  (e) {let  {arr, type , weightedList} = e.datalet  value = '' ;type ) {case  'sum' :break case  'average' :break case  'weightedAverage' :break case  'max' :break case  'middleNum' :break case  'min' :break case  'variance' :break case  'popVariance' :break case  'stdDeviation' :break case  'popStandardDeviation' :break type , value});35s变成6s 从原来的35s变成了最长6s,并且计算过程中全程无卡顿,YYDS 
time1.png src=http___img.soogif.com_n7sySW0OULhVlH5j7OrXHpbqEiM9hDsr.gif&refer=http___img.soogif.gif 
最终的效果 
table.gif 
十万条太low了,百万条数据玩一玩 // 修改上文的模拟数据let  arr = new Array(1000000).fill(1).map(() => Math.random()* 10000);let  weightedList = new Array(1000000).fill(1).map(() => Math.random()* 10000);时间明显上来了,最长要50多s了,没事玩一玩,开心就好
time3.png 
web worker 提高Canvas运行速度 web worker除了单纯进行计算外,还可以结合离屏canvas 进行绘图,提升绘图的渲染性能和使用体验
离屏canvas案例 
<template>"makeWorker" >开始绘图</button>"myCanvas"  width="300"  height="150" ></canvas>"worker-loader!./worker" ;export  default {makeWorker let  worker = new Worker();let  htmlCanvas = document.getElementById("myCanvas" );let  offscreen = htmlCanvas.transferControlToOffscreen();worker.js 
onmessage = function  (e) {let  canvas = e.data.canvas;let  ctx = canvas.getContext('2d' );"#1989fa" ;//设置填充颜色效果: 
cricle.gif 
离屏canvas的优势 
1、对于复杂的canvas绘图,可以避免阻塞主线程
2、由于这种解耦,OffscreenCanvas的渲染与DOM完全分离了开来,并且比普通Canvas速度提升了一些
Web Worker的限制 1、在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象
2、Worker中只能获取到部分浏览器提供的 API,如定时器、navigator、location、XMLHttpRequest等
3、由于可以获取XMLHttpRequest 对象,可以在 Worker 线程中执行ajax请求
4、每个线程运行在完全独立的环境中,需要通过postMessage、 message事件机制来实现的线程之间的通信
计算时长 超过多长时间 适合用Web Worker 原则上,运算时间超过50ms会造成页面卡顿,属于Long task,这种情况就可以考虑使用Web Worker 
但还要先考虑通信时长的问题
假如一个运算执行时长为100ms, 但是通信时长为300ms, 用了Web Worker可能会更慢
face.jpg 
通信时长 新建一个web worker时, 浏览器会加载对应的worker.js资源
下图中的Time是这个 js 资源的加载时长 
load.png 
最终标准: 
计算的运算时长 - 通信时长 > 50ms,推荐使用Web Worker 
场景补充说明 遇到大数据,第一反应: 为什么不让后端去计算呢? 
这里比较特殊,表格4000行,25列
即便是让后端计算,需要把大量数据传给后端,计算好再返回,这个时间也不短,还可能出现用户频繁操作,接口数据被覆盖等情况
总结 Web Worker为前端带来了后端的计算能力,扩大了前端的业务边界 
可以实现主线程与复杂计运算线程的分离,从而减轻了因大量计算而造成UI阻塞的情况
并且更大程度地利用了终端硬件的性能
本文转自 https://juejin.cn/post/7137728629986820126
如有侵权,请联系删除。