欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

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

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

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

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

以下是正文:


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

前言

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

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

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

table.jpg

答案是: 35s 左右

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

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

boom.gif

什么是假死?

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

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

致命bug

強(qiáng)行送測吧

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

絕望.jpg

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

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

如下圖所示:此次計(jì)算總用時(shí)為35.45s

重點(diǎn)從以下三個(gè)方面分析:

1)FPS

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

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

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

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

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

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

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

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

performance8.png

拿什么拯救你,我的頁面

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

R-C (1).gif

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

Web Worker了解一下:

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

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

在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()
    }
  }
  // 將計(jì)算的結(jié)果傳遞出去
  postMessage(sum);
}

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

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

4、最終代碼

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

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

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

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

total.png

對比試驗(yàn)

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

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

前戲差不多了,上硬菜

開啟多線程,并行計(jì)算

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

多線程代碼

<template>
    <div>
        <button @click="makeWorker">開始線程</button>
        <!--在計(jì)算時(shí) 往input輸入值時(shí) 沒有發(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, // 計(jì)算類型
              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}, 計(jì)算用時(shí): ${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)閉,如果還沒有計(jì)算完成,要銷毀對應(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: 計(jì)算列,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ù)字個(gè)數(shù)是奇數(shù)還是偶數(shù)
    return numberDivide(numberAdd(arr[arr.length/2-1], arr[arr.length/2]),2);//偶數(shù)個(gè)取中間兩個(gè)數(shù)的平均數(shù)
  }else{
    return arr[(arr.length+1)/2-1];//奇數(shù)個(gè)取最中間那個(gè)數(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,并且計(jì)算過程中全程無卡頓,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);

時(shí)間明顯上來了,最長要50多s了,沒事玩一玩,開心就好

time3.png

web worker 提高Canvas運(yùn)行速度

web worker除了單純進(jìn)行計(jì)算外,還可以結(jié)合離屏canvas進(jìn)行繪圖,提升繪圖的渲染性能和使用體驗(yà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ù)獲取一個(gè)OffscreenCanvas對象
                let offscreen = htmlCanvas.transferControlToOffscreen();
                // 注意:第二個(gè)參數(shù)不能省略
                worker.postMessage({canvas: offscreen}, [offscreen]);
            }
        }
    }
</script>

worker.js

onmessage = function (e) {
  // 使用OffscreenCanvas(離屏Canvas)
  let canvas = e.data.canvas;
  // 獲取繪圖上下文
  let ctx = canvas.getContext('2d');
  // 繪制一個(gè)圓弧
  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 線程的運(yùn)行環(huán)境中沒有 window 全局對象,也無法訪問 DOM 對象

2、Worker中只能獲取到部分瀏覽器提供的 API,如定時(shí)器、navigator、locationXMLHttpRequest

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

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

計(jì)算時(shí)長 超過多長時(shí)間 適合用Web Worker

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

但還要先考慮通信時(shí)長的問題

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

face.jpg

通信時(shí)長

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

下圖中的Time是這個(gè) js 資源的加載時(shí)長

load.png

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

計(jì)算的運(yùn)算時(shí)長 - 通信時(shí)長 > 50ms,推薦使用Web Worker

場景補(bǔ)充說明

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

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

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


總結(jié)

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

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

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

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

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


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