web?worker在項(xiàng)目中的使用學(xué)習(xí)為項(xiàng)目增加亮點(diǎn)
引言
平時(shí)小伙伴們不是說日常的項(xiàng)目開發(fā)中,都是單純的搬磚,沒啥亮點(diǎn)嘛,那現(xiàn)在就來啦!
咱們今天就來聊聊web worker,這可是面試官最最最喜歡的的性能優(yōu)化哦~
為什么JavaScript是單線程?
總所周知,JavaScript語言的特點(diǎn)是單線程,也就是說,同一個(gè)時(shí)間只能做一件事。那么,為什么JavaScript不能有多個(gè)線程呢?這樣能提高效率啊。
JavaScript的單線程,與它的用途有關(guān)。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復(fù)雜的同步問題。比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?
什么是Web Worker?
為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒有改變JavaScript單線程的本質(zhì)。
在worker線程中,雖然無法直接操作dom節(jié)點(diǎn),也不能使用window對象的默認(rèn)方法和屬性,但是仍然可以使用window對象下的東西,比如websocket,indexedDB等。
workers 和主線程間的數(shù)據(jù)傳遞通過這樣的消息機(jī)制進(jìn)行——雙方都使用postMessage() 方法發(fā)送各自的消息,使用 onmessage 事件處理函數(shù)來響應(yīng)消息(消息被包含在Message事件的 data 屬性中)。這個(gè)過程中數(shù)據(jù)并不是被共享而是被復(fù)制。
關(guān)于web worker的兼容性問題,在can i use中查找一輪后發(fā)現(xiàn),基本目前所有主流的瀏覽器都支持了,因此放心食用,無需考慮兼容性的問題。
小試牛刀
前面學(xué)習(xí)了那么多武功秘籍,少俠們,確定不來一展身手嗎?
小羽這里簡單的寫了一個(gè)小demo,這個(gè)demo的內(nèi)容就是遞歸獲取斐波那契數(shù)列。會分為單線程和多線程模式,然后分別測試運(yùn)行20次fb方法所需要的時(shí)間。
- 單線程模式:利用for循環(huán)直接執(zhí)行20次fb,統(tǒng)計(jì)執(zhí)行時(shí)間
- 多線程模式:利用for循環(huán),創(chuàng)建多個(gè)worker線程。并使用promise.all處理這些異步的worker線程,等待所有的worker執(zhí)行完成后,統(tǒng)計(jì)執(zhí)行時(shí)間
<!-- * @Author: xiaoyu * @Description: * @Date: 2022-05-08 08:40:54 * @LastEditors: xiaoyu * @LastEditTime: 2022-06-29 23:19:40 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>web worker</title> </head> <script> const number = 20 // 運(yùn)行次數(shù) // 多線程測試 function workerTest() { console.log('%c 開始多線程測試 ', 'color:#fff; background:#00897b ') const workerList = [] for (let i = 0; i < number; i++) { const workerItem = new Promise((resolve, reject) => { const myWorker = new Worker('worker.js') myWorker.postMessage({ function: 'fb', data: 43 }) myWorker.onmessage = (e) => { resolve(e.data) // 關(guān)閉worker線程 myWorker.terminate() } }) workerList.push(workerItem) } console.time('worker多線程執(zhí)行時(shí)間') Promise.all(workerList).then(res => { console.log(res) console.timeEnd('worker多線程執(zhí)行時(shí)間') }) } function singleTest() { console.log('%c 開始單線程測試 ', 'color:#fff; background:#00897b ') function fb(n) { if (n === 1 || n === 2) { return 1; } return fb(n - 1) + fb(n - 2) } console.time('單線程執(zhí)行時(shí)間') for (let i = 0; i < number; i++) { const res = fb(43) console.log({ data: res, name: 'single test' }) } console.timeEnd('單線程執(zhí)行時(shí)間') } </script> <body> <button onclick="singleTest()">單線程測試</button> <button onclick="workerTest()">多線程測試</button> </body> </html>
/* * @Author: xiaoyu * @Description: worker 線程 * @Date: 2022-05-08 08:41:30 * @LastEditors: xiaoyu * @LastEditTime: 2022-06-29 23:17:44 */ // 方法對象 const funcObj = { fb: (n) => { if(n===1 || n ===2){ return 1; } return funcObj.fb(n-1) + funcObj.fb(n-2) } } // onmessage事件 onmessage = function(e){ const {data} = e; const res = funcObj[data.function](data.data) // 將獲取的數(shù)據(jù)通過postMessage發(fā)送到主線程 self.postMessage({ data: res, name: 'worker test' }) self.close() }
打開任務(wù)管理器,點(diǎn)擊單線程測試按鈕進(jìn)行單線程的測試??梢詮南聢D發(fā)現(xiàn),單線程的調(diào)用時(shí)間約為70s,cpu的調(diào)用基本上也就只是兩個(gè)核心在切換工作,小羽在多次測試后,其實(shí)是有多個(gè)核心在切換工作,不過單一時(shí)間只有一個(gè)核心是在滿載工作(遞歸獲取斐波那契數(shù)列)。
同樣是打開任務(wù)管理器,然后點(diǎn)擊多線程測試按鈕。此時(shí)咱們的cpu就不再偷懶了,直接16線程滿載運(yùn)行,只需要7.9s就完成了20次遞歸獲取斐波那契數(shù)列。
咱們簡單的計(jì)算一下使用web worker多線程提升效果:(70750-7973)/7973 ≈7.87。即提升了7.87倍的效率。當(dāng)然這是在8核16線程上的電腦上跑了,如果在核心數(shù)不同的cpu上這個(gè)倍數(shù)也是會發(fā)生相應(yīng)的變化
在單頁面應(yīng)用中使用
通過上面的例子,是不是so easy呀?
好啦,那咱們就算掌握了web worker的基本使用方法啦。
但是在react、vue等單頁面應(yīng)用中,webpack/vite通常會將js代碼打包成一個(gè)js文件。因此通過上面的new Worker('worker.js')的方式來新建worker,將會報(bào)訪問不到worker.js的錯(cuò)誤。
所以,在單頁面應(yīng)用中,咱們該怎么使用web worker呢?
方案1:既然webpack/vite會將js的代碼打包成一個(gè)js文件,那咱們不讓它打包不就好了。而單頁面應(yīng)用的工程下,通常都是會有一個(gè)public的靜態(tài)資源目錄,咱們將worker.js放入其中即可。
方案2:webpack4及以下的版本可以使用worker-loader
方案3:webpack5/vite則可以使用new Worker(new URL('worker.js', import.meta.url))的方式
import React from 'react' export default function WebWorkerTest() { const handleClick = () => { const number = 1 const workerList = [] console.log('%c 開始多線程測試 ', 'color:#fff; background:#00897b ') for (let i = 0; i < number; i++) { const workerItem = new Promise((resolve, reject) => { const myWorker = new Worker(new URL('../utils/fb.worker.ts', import.meta.url)) myWorker.postMessage({ function: 'fb', data: 43 }) myWorker.onmessage = (e) => { resolve(e.data) // 關(guān)閉worker線程 myWorker.terminate() } }) workerList.push(workerItem) } console.time('worker多線程執(zhí)行時(shí)間') Promise.all(workerList).then(res => { console.log(res) console.timeEnd('worker多線程執(zhí)行時(shí)間') }) } return ( <> <button onClick={handleClick}>vite/webpack5</button> </> ) }
// fb.worker.ts // 方法對象 const funcObj = { fb: (n: number): number => { if (n === 1 || n === 2) { return 1; } return funcObj.fb(n - 1) + funcObj.fb(n - 2); }, }; // onmessage事件 onmessage = function (e) { const { data } = e; const res = funcObj[data.function](data.data); // 將獲取的數(shù)據(jù)通過postMessage發(fā)送到主線程 self.postMessage({ data: res, name: "worker test", }); self.close(); };
注意事項(xiàng)
雖然web worker可以調(diào)用cpu的多線程,從而提高咱們頁面的性能。但是它不是隨便使用的,如果濫用web worker可能不僅不會得到性能的提升,還可能造成性能的損耗。
舉一個(gè)簡單的小栗子
如果咱們將遞歸獲取斐波那契數(shù)列第n位的方法,將傳入?yún)?shù)修改為第2位,這時(shí)候咱們再重跑單線程測試和多線程測試。
結(jié)果如下圖,咱們可以發(fā)現(xiàn)單線程模式下,獲取20次斐波那契數(shù)列第二位的時(shí)間僅需要1.5ms,而在多線程的情況下卻需要78ms。這是為什么呢?因?yàn)樵蹅兠看蝿?chuàng)建worker線程以及possmessage通信都是需要損耗一些性能以及時(shí)間的。因此web worker是不可以濫用的哦,日常開發(fā)中,建議在需要消耗比較多的cpu運(yùn)算能力的時(shí)候酌情使用。
小結(jié)
本文通過了幾個(gè)簡單的小栗子,帶大家學(xué)習(xí)web worker的基本知識,使用方法,以及需要注意的事項(xiàng)。小伙伴們在日常的開發(fā)中可以按需嘗試哦,讓自己的項(xiàng)目中多些亮點(diǎn),也可以讓面試官眼前一亮哦。
更多關(guān)于web worker使用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Svelte嵌套組件preventDefault構(gòu)建Web應(yīng)用
這篇文章主要介紹了Svelte嵌套組件preventDefault構(gòu)建Web應(yīng)用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12JavaScript復(fù)原何同學(xué)B站頭圖細(xì)節(jié)示例詳解
這篇文章主要為大家介紹了JavaScript復(fù)原何同學(xué)B站頭圖細(xì)節(jié)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07Dom-api MutationObserver使用方法詳解
這篇文章主要為大家介紹了Dom-api MutationObserver使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11一款功能強(qiáng)大的markdown編輯器tui.editor使用示例詳解
這篇文章主要為大家介紹了一款功能強(qiáng)大的markdown編輯器tui.editor使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02