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

模擬耗時任務(wù)
看如下代碼,使用一個超大的 for 循環(huán),模擬 JS 中的耗時任務(wù),讓代碼執(zhí)行時主線程卡頓,還原假死現(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ù)耗時')
for (let i = 0; i < 10000000000; i++) {
n += i
}
console.timeEnd('任務(wù)耗時')
})
})();
</script>
執(zhí)行結(jié)果:

可以看到點擊 開始復(fù)雜任務(wù) 按鈕時,在計時器的第 4
秒主線程卡主了將近 4 秒,然后再恢復(fù)運行,這就是單線程中的 JS 耗時任務(wù)導致的頁面假死現(xiàn)象。
使用 Web Worker 解決耗時問題
看了上面的耗時任務(wù)導致頁面假死,再使用 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ù)耗時')
}
worker.onerror = event => {
console.error('Worker 異常:', event)
}
document.getElementById('start').addEventListener('click', () => {
console.time('任務(wù)耗時')
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ù)耗時長短差不多,但是主線程在點擊按鈕之后并沒有進入假死狀態(tài),定時器還是在順利執(zhí)行,所以 Web Worker 中運行的復(fù)雜任務(wù)并不會影響主線程的任務(wù)調(diào)度。
Web Worker 限制
在子線程中運行的代碼,無法直接操作 DOM,無法訪問 window/document 對象,也無法使用 localStorage 等,如果使用這些 API,代碼將會報錯:

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

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