最初的 JS 執(zhí)行代碼都是一條線執(zhí)行到底,當(dāng)遇到比較耗時(shí)的操作時(shí),比如大數(shù)組循環(huán)運(yùn)算,就會(huì)導(dǎo)致頁面卡著,就像假死一樣。就像一個(gè)人在廚房燒菜一樣,需要依次完成切菜、炒菜、裝盤這些步驟,此過程中沒辦法同時(shí)做其他事情,必須按順序執(zhí)行每一個(gè)步驟。
Web Worker
賦予了 JS 分配任務(wù)的能力,在遇到復(fù)雜的計(jì)算型任務(wù)時(shí),比如 canvas
圖形圖像處理(添加濾鏡、矩陣變換等),此類不依賴 DOM 操作的計(jì)算型任務(wù)都可以交由 Web Worker 來處理,這樣不會(huì)阻塞主線程的任務(wù)調(diào)度,從而提升前端的代碼運(yùn)行速度。
任務(wù)時(shí)序圖

模擬耗時(shí)任務(wù)
看如下代碼,使用一個(gè)超大的 for 循環(huán),模擬 JS 中的耗時(shí)任務(wù),讓代碼執(zhí)行時(shí)主線程卡頓,還原假死現(xiàn)象:
<div id="output"></div>
<button id="start">開始復(fù)雜任務(wù)</button>
<script>
(() => {
let times = 0
const output = document.getElementById('output')
function loop () {
setTimeout(() => {
times ++
output.innerText = times
loop()
}, 1000);
}
loop()
document.getElementById('start').addEventListener('click', () => {
let n = 0
console.time('任務(wù)耗時(shí)')
for (let i = 0; i < 10000000000; i++) {
n += i
}
console.timeEnd('任務(wù)耗時(shí)')
})
})();
</script>
執(zhí)行結(jié)果:

可以看到點(diǎn)擊 開始復(fù)雜任務(wù) 按鈕時(shí),在計(jì)時(shí)器的第 4
秒主線程卡主了將近 4 秒,然后再恢復(fù)運(yùn)行,這就是單線程中的 JS 耗時(shí)任務(wù)導(dǎo)致的頁面假死現(xiàn)象。
使用 Web Worker 解決耗時(shí)問題
看了上面的耗時(shí)任務(wù)導(dǎo)致頁面假死,再使用 Web Worker 來重寫一下上面代碼:
main.html
<div id="output"></div>
<button id="start">開始復(fù)雜任務(wù)</button>
<script>
(() => {
let times = 0
const output = document.getElementById('output')
function loop () {
setTimeout(() => {
times ++
output.innerText = times
loop()
}, 1000);
}
loop()
const worker = new Worker('./worker.js')
worker.onmessage = event => {
console.log(event.data)
console.timeEnd('任務(wù)耗時(shí)')
}
worker.onerror = event => {
console.error('Worker 異常:', event)
}
document.getElementById('start').addEventListener('click', () => {
console.time('任務(wù)耗時(shí)')
worker.postMessage(10000000000)
})
})();
</script>
worker.js
self.onmessage = event => {
let n = 0
let max = event.data
for (let i = 0; i < max; i++) {
n += i
}
postMessage(n)
}
執(zhí)行結(jié)果:

可以看到雖然任務(wù)耗時(shí)長短差不多,但是主線程在點(diǎn)擊按鈕之后并沒有進(jìn)入假死狀態(tài),定時(shí)器還是在順利執(zhí)行,所以 Web Worker 中運(yùn)行的復(fù)雜任務(wù)并不會(huì)影響主線程的任務(wù)調(diào)度。
Web Worker 限制
在子線程中運(yùn)行的代碼,無法直接操作 DOM,無法訪問 window/document 對(duì)象,也無法使用 localStorage 等,如果使用這些 API,代碼將會(huì)報(bào)錯(cuò):

for 循環(huán)優(yōu)化
注意上述代碼中的 max
變量,為什么需要一個(gè)變量來保存 event.data
值?而不是直接使用 event.data
循環(huán)?將 worker.js 改造一下,看看不同使用方式的任務(wù)耗時(shí):
self.onmessage = event => {
console.time('max 循環(huán)耗時(shí)')
let n = 0
let max = event.data
for (let i = 0; i < max; i++) {
n += i
}
console.timeEnd('max 循環(huán)耗時(shí)')
console.time('Object 循環(huán)耗時(shí)')
let m = 0
for (let i = 0; i < event.data; i++) {
m += i
}
console.timeEnd('Object 循環(huán)耗時(shí)')
postMessage(n)
}
main.html
(() => {
const worker = new Worker('./worker.js')
document.getElementById('start').addEventListener('click', () => {
worker.postMessage(100000000)
})
})();
執(zhí)行結(jié)果:

可以明顯看到,性能耗時(shí)相差將近 6 倍,這數(shù)字會(huì)隨著對(duì)象屬性越多,耗時(shí)越長?。?strong style="margin: 0px; padding: 0px;">所以在循環(huán)中應(yīng)當(dāng)盡量避免讀取對(duì)象屬性,盡可能使用變量來做循環(huán)條件?。?/strong>
寫在最后
可以使用 Web Worker 同時(shí)啟用多個(gè)工作線程,只是在任務(wù)調(diào)度的時(shí)候,需要注意響應(yīng)結(jié)果的先后順序是否對(duì)主線程的運(yùn)行有影響。
一些復(fù)雜的計(jì)算任務(wù)(比如視頻轉(zhuǎn)碼,圖片壓縮,圖片處理等),都丟給子線程處理吧,咱們前端也可以玩玩多線程~~
?轉(zhuǎn)自https://www.cnblogs.com/linx/p/19069469
該文章在 2025/9/10 9:13:05 編輯過