人妻夜夜爽天天爽三区丁香花-人妻夜夜爽天天爽三-人妻夜夜爽天天爽欧美色院-人妻夜夜爽天天爽免费视频-人妻夜夜爽天天爽-人妻夜夜爽天天

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

一文徹底了解Web Worker,十萬條數(shù)據(jù)都是弟弟

admin
2025年1月6日 10:58 本文熱度 453

今天為大家分享一篇關(guān)于web worker的優(yōu)質(zhì)文章,讓你了解一下如何通過Web Worker來解決前端處理大量數(shù)據(jù)運算時頁面假死的問題。

以下是正文:


如何讓前端擁有后端的計算能力,在算力緊缺的年代,擴展前端的業(yè)務(wù)邊界!

前言

頁面中有十萬條數(shù)據(jù),對其進(jìn)行復(fù)雜運算,需要多久呢?

表格4000行,25列,共十萬條數(shù)據(jù)

運算包括:總和、算術(shù)平均、加權(quán)平均、最大、最小、計數(shù)、樣本標(biāo)準(zhǔn)差、樣本方差、中位數(shù)、總體標(biāo)準(zhǔn)差、總體方差

table.jpg

答案是: 35s 左右

注:具體時間根據(jù)電腦配置會有所不同

并且 這個時間段內(nèi),頁面一直處于假死狀態(tài),對頁面做任何操作都沒有反應(yīng)??????

boom.gif

什么是假死?

瀏覽器有GUI渲染線程與JS引擎線程,這兩個線程是互斥的關(guān)系

當(dāng)js有大量計算時,會造成 UI 阻塞,出現(xiàn)界面卡頓、掉幀等情況,嚴(yán)重時會出現(xiàn)頁面卡死的情況,俗稱假死

致命bug

強行送測吧

測試小姐姐:你的頁面又死了!!
我:還沒有死,在ICU…… ,過一會就好了
測試小姐姐:已經(jīng)等了好一會了,還不行啊,是個致命bug??
我:……

絕望.jpg

闖蕩前端數(shù)十載,竟被提了個致命bug,顏面何在!??

Performance分析假死期間的性能表現(xiàn)

如下圖所示:此次計算總用時為35.45s

重點從以下三個方面分析:

1)FPS

FPS: 表示每秒傳輸幀數(shù),是分析動畫的一個主要性能指標(biāo),綠色的長度越長,用戶體驗越好;反之紅色越長,說明卡頓嚴(yán)重

從圖中看到FPS中有一條持續(xù)了35s的紅線,說明這期間卡頓嚴(yán)重

2)火焰圖Main
Main: 表示主線程運行狀況,包括js的計算與執(zhí)行、css樣式計算、Layout布局等等。

展開Main,紅色倒三角的為Long Task,執(zhí)行時長50ms就屬于長任務(wù),會阻塞頁面渲染

從圖中看到計算過程的Long Task執(zhí)行時間為35.45s, 是造成頁面假死的原因

3)Summary 統(tǒng)計匯總面板
Summary: 表示各指標(biāo)時間占用統(tǒng)計報表

  • Loading: 加載時間
  • Scripting: js計算時間
  • Rendering: 渲染時間
  • Painting: 繪制時間
  • Other: 其他時間
  • Idle: 瀏覽器閑置時間

Scripting代碼執(zhí)行為35.9s

performance8.png

拿什么拯救你,我的頁面

召喚W(wǎng)eb Worker,出來吧神龍

R-C (1).gif

神龍,我想讓頁面的計算變快,并且不卡頓

Web Worker了解一下:

在HTML5的新規(guī)范中,實現(xiàn)了 Web Worker 來引入 js 的 “多線程” 技術(shù), 可以讓我們在頁面主運行的 js 線程中,加載運行另外單獨的一個或者多個 js 線程

一句話:Web Worker專門處理復(fù)雜計算的,從此讓前端擁有后端的計算能力

在Vue中 使用 Web Worker

1、安裝worker-loader

npm install worker-loader

2、編寫worker.js

onmessage = function (e) {
  // onmessage獲取傳入的初始值
  let sum = e.data;
  for (let i = 0; i < 200000; i++) {
    for (let i = 0; i < 10000; i++) {
      sum += Math.random()
    }
  }
  // 將計算的結(jié)果傳遞出去
  postMessage(sum);
}

3、通過行內(nèi)loader 引入 worker.js

import Worker from "worker-loader!./worker"

4、最終代碼

<template>
    <div>
        <button @click="makeWorker">開始線程</button>
        <!--在計算時 往input輸入值時 沒有發(fā)生卡頓-->
        <p><input type="text"></p>
    </div>
</template>

<script>
    import Worker from "worker-loader!./worker";

    export default {
        methods: {
            makeWorker() {
                // 獲取計算開始的時間
                let start = performance.now();
                // 新建一個線程
                let worker = new Worker();
                // 線程之間通過postMessage進(jìn)行通信
                worker.postMessage(0);
                // 監(jiān)聽message事件
                worker.addEventListener("message", (e) => {
                    // 關(guān)閉線程
                    worker.terminate();
                    // 獲取計算結(jié)束的時間
                    let end = performance.now();
                    // 得到總的計算時間
                    let durationTime = end - start;
                    console.log('計算結(jié)果:', e.data);
                    console.log(`代碼執(zhí)行了 ${durationTime} 毫秒`);
                });
            }
        },
    }
</script>

計算過程中,在input框輸入值,頁面一直未發(fā)生卡頓

total.png

對比試驗

如果直接把下面這段代碼直接丟到主線程中,計算過程中頁面一直處于假死狀態(tài),input框無法輸入

let sum = 0;
for (let i = 0; i < 200000; i++) {
    for (let i = 0; i < 10000; i++) {
      sum += Math.random()
    }
  }

前戲差不多了,上硬菜

開啟多線程,并行計算

回到要解決的問題,執(zhí)行多種運算時,給每種運算開啟單獨的線程,線程計算完成后要及時關(guān)閉

多線程代碼

<template>
    <div>
        <button @click="makeWorker">開始線程</button>
        <!--在計算時 往input輸入值時 沒有發(fā)生卡頓-->
        <p><input type="text"></p>
    </div>
</template>

<script>
    import Worker from "worker-loader!./worker";

    export default {
        data() {
          // 模擬數(shù)據(jù)
          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: '算術(shù)平均'},
              {type'weightedAverage', name: '加權(quán)平均'},
              {type'max', name: '最大'},
              {type'middleNum', name: '中位數(shù)'},
              {type'min', name: '最小'},
              {type'variance', name: '樣本方差'},
              {type'popVariance', name: '總體方差'},
              {type'stdDeviation', name: '樣本標(biāo)準(zhǔn)差'},
              {type'popStandardDeviation', name: '總體標(biāo)準(zhǔn)差'}
          ]
          return {
              workerList: [], // 用來存儲所有的線程
              calcList, // 計算類型
              arr, // 數(shù)據(jù)
              weightedList // 加權(quán)因子
          }
        },
        methods: {
            makeWorker() {
                this.calcList.forEach(item => {
                    let workerName = `worker${this.workerList.length}`;
                    let worker = new Worker();
                    let start = performance.now();
                    worker.postMessage({arr: this.arr, type: item.type, weightedList: this.weightedList});
                    worker.addEventListener("message", (e) => {
                        worker.terminate();

                        let tastName = '';
                        this.calcList.forEach(item => {
                            if(item.type === e.data.type) {
                                item.value = e.data.value;
                                tastName = item.name;
                            }
                        })

                        let end = performance.now();
                        let duration = end - start;
                        console.log(`當(dāng)前任務(wù): ${tastName}, 計算用時: ${duration} 毫秒`);
                    });
                    this.workerList.push({ [workerName]: worker });
                })
            },
            clearWorker() {
                if (this.workerList.length > 0) {
                    this.workerList.forEach((item, key) => {
                        item[`worker${key}`].terminate && item[`worker${key}`].terminate(); // 終止所有線程
                    });
                }
            }
        },
        // 頁面關(guān)閉,如果還沒有計算完成,要銷毀對應(yīng)線程
        beforeDestroy() {
            this.clearWorker();
        },
    }
</script>

worker.js

import { create, all } from 'mathjs'
const config = {
  number: 'BigNumber',
  precision: 20 // 精度
}
const math = create(all, config);

//加
const numberAdd = (arg1,arg2) => {
  return math.number(math.add(math.bignumber(arg1), math.bignumber(arg2)));
}
//減
const numberSub = (arg1,arg2) => {
  return math.number(math.subtract(math.bignumber(arg1), math.bignumber(arg2)));
}
//乘
const numberMultiply = (arg1, arg2) => {
  return math.number(math.multiply(math.bignumber(arg1), math.bignumber(arg2)));
}
//除
const numberDivide = (arg1, arg2) => {
  return math.number(math.divide(math.bignumber(arg1), math.bignumber(arg2)));
}

// 數(shù)組總體標(biāo)準(zhǔn)差公式
const popVariance = (arr) => {
  return Math.sqrt(popStandardDeviation(arr))
}

// 數(shù)組總體方差公式
const popStandardDeviation = (arr) => {
  let s,
    ave,
    sum = 0,
    sums= 0,
    len = arr.length;
  for (let i = 0; i < len; i++) {
    sum = numberAdd(Number(arr[i]), sum);
  }
  ave = numberDivide(sum, len);
  for(let i = 0; i < len; i++) {
    sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))
  }
  s = numberDivide(sums,len)
  return s;
}

// 數(shù)組加權(quán)公式
const weightedAverage = (arr1, arr2) => { // arr1: 計算列,arr2: 選擇的權(quán)重列
  let s,
    sum = 0, // 分子的值
    sums= 0, // 分母的值
    len = arr1.length;
  for (let i = 0; i < len; i++) {
    sum = numberAdd(numberMultiply(Number(arr1[i]), Number(arr2[i])), sum);
    sums = numberAdd(Number(arr2[i]), sums);
  }
  s = numberDivide(sum,sums)
  return s;
}

// 數(shù)組樣本方差公式
const variance = (arr) => {
  let s,
    ave,
    sum = 0,
    sums= 0,
    len = arr.length;
  for (let i = 0; i < len; i++) {
    sum = numberAdd(Number(arr[i]), sum);
  }
  ave = numberDivide(sum, len);
  for(let i = 0; i < len; i++) {
    sums = numberAdd(sums, numberMultiply(numberSub(Number(arr[i]), ave), numberSub(Number(arr[i]), ave)))
  }
  s = numberDivide(sums,(len-1))
  return s;
}

// 數(shù)組中位數(shù)
const middleNum = (arr) => {
  arr.sort((a,b) => a - b)
  if(arr.length%2 === 0){ //判斷數(shù)字個數(shù)是奇數(shù)還是偶數(shù)
    return numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶數(shù)個取中間兩個數(shù)的平均數(shù)
  }else{
    return arr[(arr.length+1)/2-1];//奇數(shù)個取最中間那個數(shù)
  }
}

// 數(shù)組求和
const sum = (arr) => {
  let sum = 0, len = arr.length;
  for (let i = 0; i < len; i++) {
    sum = numberAdd(Number(arr[i]), sum);
  }
  return sum;
}

// 數(shù)組平均值
const average = (arr) => {
  return numberDivide(sum(arr), arr.length)
}

// 數(shù)組最大值
const max = (arr) => {
  let max = arr[0]
  for (let i = 0; i < arr.length; i++) {
    if(max < arr[i]) {
      max = arr[i]
    }
  }
  return max
}

// 數(shù)組最小值
const min = (arr) => {
  let min = arr[0]
  for (let i = 0; i < arr.length; i++) {
    if(min > arr[i]) {
      min = arr[i]
    }
  }
  return min
}

// 數(shù)組有效數(shù)據(jù)長度
const count = (arr) => {
  let remove = [''' ', null , undefined, '-']; // 排除無效的數(shù)據(jù)
  return arr.filter(item => !remove.includes(item)).length
}

// 數(shù)組樣本標(biāo)準(zhǔn)差公式
const stdDeviation = (arr) => {
  return Math.sqrt(variance(arr))
}

// 數(shù)字三位加逗號,保留兩位小數(shù)
const formatNumber = (num, pointNum = 2) => {
  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]}`
}

onmessage = function (e) {

  let {arr, type, weightedList} = e.data
  let value = '';
  switch (type) {
    case 'sum':
      value = formatNumber(sum(arr));
      break
    case 'average':
      value = formatNumber(average(arr));
      break
    case 'weightedAverage':
      value = formatNumber(weightedAverage(arr, weightedList));
      break
    case 'max':
      value = formatNumber(max(arr));
      break
    case 'middleNum':
      value = formatNumber(middleNum(arr));
      break
    case 'min':
      value = formatNumber(min(arr));
      break
    case 'variance':
      value = formatNumber(variance(arr));
      break
    case 'popVariance':
      value = formatNumber(popVariance(arr));
      break
    case 'stdDeviation':
      value = formatNumber(stdDeviation(arr));
      break
    case 'popStandardDeviation':
      value = formatNumber(popStandardDeviation(arr));
      break
    }

  // 發(fā)送數(shù)據(jù)事件
  postMessage({type, value});
}

35s變成6s

從原來的35s變成了最長6s,并且計算過程中全程無卡頓,YYDS

time1.png
src=http___img.soogif.com_n7sySW0OULhVlH5j7OrXHpbqEiM9hDsr.gif&refer=http___img.soogif.gif

最終的效果

table.gif

十萬條太low了,百萬條數(shù)據(jù)玩一玩

// 修改上文的模擬數(shù)據(jù)
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除了單純進(jìn)行計算外,還可以結(jié)合離屏canvas進(jìn)行繪圖,提升繪圖的渲染性能和使用體驗

離屏canvas案例

<template>
    <div>
        <button @click="makeWorker">開始繪圖</button>
        <canvas id="myCanvas" width="300" height="150"></canvas>
    </div>
</template>

<script>
    import Worker from "worker-loader!./worker";
    export default {
        methods: {
            makeWorker() {
                let worker = new Worker();
                let htmlCanvas = document.getElementById("myCanvas");
                // 使用canvas的transferControlToOffscreen函數(shù)獲取一個OffscreenCanvas對象
                let offscreen = htmlCanvas.transferControlToOffscreen();
                // 注意:第二個參數(shù)不能省略
                worker.postMessage({canvas: offscreen}, [offscreen]);
            }
        }
    }
</script>

worker.js

onmessage = function (e) {
  // 使用OffscreenCanvas(離屏Canvas)
  let canvas = e.data.canvas;
  // 獲取繪圖上下文
  let ctx = canvas.getContext('2d');
  // 繪制一個圓弧
  ctx.beginPath() // 開啟路徑
  ctx.arc(150, 75, 50, 0, Math.PI*2);
  ctx.fillStyle="#1989fa";//設(shè)置填充顏色
  ctx.fill();//開始填充
  ctx.stroke();
}

效果:

cricle.gif

離屏canvas的優(yōu)勢

1、對于復(fù)雜的canvas繪圖,可以避免阻塞主線程

2、由于這種解耦,OffscreenCanvas的渲染與DOM完全分離了開來,并且比普通Canvas速度提升了一些

Web Worker的限制

1、在 Worker 線程的運行環(huán)境中沒有 window 全局對象,也無法訪問 DOM 對象

2、Worker中只能獲取到部分瀏覽器提供的 API,如定時器navigatorlocationXMLHttpRequest

3、由于可以獲取XMLHttpRequest 對象,可以在 Worker 線程中執(zhí)行ajax請求

4、每個線程運行在完全獨立的環(huán)境中,需要通過postMessage、 message事件機制來實現(xiàn)的線程之間的通信

計算時長 超過多長時間 適合用Web Worker

原則上,運算時間超過50ms會造成頁面卡頓,屬于Long task,這種情況就可以考慮使用Web Worker

但還要先考慮通信時長的問題

假如一個運算執(zhí)行時長為100ms, 但是通信時長為300ms, 用了Web Worker可能會更慢

face.jpg

通信時長

新建一個web worker時, 瀏覽器會加載對應(yīng)的worker.js資源

下圖中的Time是這個 js 資源的加載時長

load.png

最終標(biāo)準(zhǔn):

計算的運算時長 - 通信時長 > 50ms,推薦使用Web Worker

場景補充說明

遇到大數(shù)據(jù),第一反應(yīng): 為什么不讓后端去計算呢?

這里比較特殊,表格4000行,25列
1)用戶可以對表格進(jìn)行靈活操作,比如刪除任何行或列,選擇或剔除任意行
2)用戶可以靈活選擇運算的類型,計算一個或多個

即便是讓后端計算,需要把大量數(shù)據(jù)傳給后端,計算好再返回,這個時間也不短,還可能出現(xiàn)用戶頻繁操作,接口數(shù)據(jù)被覆蓋等情況


總結(jié)

Web Worker為前端帶來了后端的計算能力,擴大了前端的業(yè)務(wù)邊界

可以實現(xiàn)主線程與復(fù)雜計運算線程的分離,從而減輕了因大量計算而造成UI阻塞的情況

并且更大程度地利用了終端硬件的性能

本文轉(zhuǎn)自 https://juejin.cn/post/7137728629986820126

如有侵權(quán),請聯(lián)系刪除。


該文章在 2025/1/6 10:58:03 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 日夜影院永久入口天天综合 | 欧美伊香蕉久久综合类网站 | 18无码粉嫩小泬无套在线观看 | 亚洲精品久久久WWW 亚洲精品久久久WWW小说 | 97s色视频一区二区三区在线 | 蜜臀久久99精品久久久久久网站 | 亚洲欧美日韩成人网 | 国内揄拍国内精品对白86 | 纯肉巨黄H爆粗口男男分卷阅读 | 久久久国产精品免费a片分 久久久国产精品免费A片分环卫 | 国产精品一区二区久久不卡一级黄色毛片 | 99久久久无码国产精品免费 | 一级无码亚洲 | 四虎影视在线看免费 720p | 亚洲无精品一区二区在线观看 | 午夜精品国产福利在线观看 | 五月婷婷天天干天天日 | 久久久精品久久久久三级 | 99精品一区二区三区视频 | 免费永久观看美女视频网站网址 | 国产麻豆秘麻豆 | 性欧美国产高清在线观看 | 精品久久久久久无码 | 狠狠色狠狠色综合日日92 | 欧美日韩整片中文字幕 | 欧美激情一区二区三区啪啪 | 中国av在线播放 | 换脸国产AV一区二区三区 | 日本伊人色综合网 | 国产成人三级电影在线观看 | 在线无码天堂视频 | www国产成人免费观看视频 | 国产一区二区免费看 | 亚洲欧美日韩精品一区在线观看 | 苍井空一区二区三区爱 | 欧美日韩精品免费一区二区三区 | 日韩精品一区二区三区中文版 | 2024国产午夜福利久久 | 国产成人精品久久综合电影 | 免费看a一级毛片 | 2024亚洲国产精品无码最新 |