瀏覽器執(zhí)行環(huán)境是單線程的,一旦出現(xiàn)【主線程】耗時操作,就會造成瀏覽器卡死,用戶點擊沒響應(yīng)等情況。
Web Worker 可以創(chuàng)建一個獨立于主線程運行的子線程。可以將一些【可能會阻塞主線程的操作】,丟在 Worker 里去單獨執(zhí)行。
那為什么平時都沒有意識到主線程阻塞這個問題呢?
因為下大多數(shù)情況下,我們不太關(guān)心瀏覽器主線程是否會被阻塞,因為同步代碼執(zhí)行一般都很快,慢的I/O、異步請求、定時器等操作,瀏覽器默認(rèn)就幫我們異步操作了(變成宏、微任務(wù)了)。
比如:
- 異步請求
- 定時器
- 宏、微任務(wù)等。
阻塞主線程 - 一般不會出現(xiàn)
事實上,一般項目開發(fā)中,很難會用阻塞主線程的業(yè)務(wù)邏輯代碼出現(xiàn)。 但是為了驗證這個問題,這里可以模擬一個非常尷尬的場景。
// 創(chuàng)建一個 sleep 函數(shù),模擬主線程阻塞的情況
function sleep (wait = 5000) {
let now = new Date()
while (new Date() - now < wait) { }
}
sleep() // 阻塞主線程
document.addEventListener.call(btn, "click", function () {
alert('點我有反應(yīng)嗎?')
})
我們會在瀏覽器上放一個按鈕,并給按鈕綁定點擊事件。
但由于 sleep 耗時 5 秒鐘,在此過程中,主線程就阻塞了。

可以發(fā)現(xiàn),由于主線程被阻塞,瀏覽器都無法正常渲染了,都出現(xiàn)了黑屏。 點擊按鈕也半天沒反應(yīng)。
使用 Worker 解決主線程耗時造成的阻塞問題
可以將主線程一些耗時的【同步】操作,丟給 Worker 來處理,將耗時操作丟在子線程中,這樣主線程就不會被阻塞,就正常處理渲染,處理用戶點擊,異步回到等。
WebWorker 的作用,就是為了 JavaScript 創(chuàng)建多線程環(huán)境,允許主線程創(chuàng)建 Worker 子線程,將一些【可能會阻塞主線程】的任務(wù)分配給 worker 子線程去處理。兩者互不干擾。等子線程處理完成之后,在通過消息的機制把處理結(jié)果返回給主線程即可。充分利用多線程的優(yōu)勢。
Worker 線程畢竟占據(jù)的一個操作系統(tǒng)的線程資源,在 Worker 的任務(wù)執(zhí)行完畢之后,即可將其關(guān)閉,釋放操作系統(tǒng)線程資源。
Worker 使用說明和 API
使用說明
- 同源限制
分配給 worker 線程的腳本,必須和主線程腳本同源。(否則無法創(chuàng)建 worker,且雙方無法通信)
- DOM限制
Worker 工作在子線程,和主線程不太一樣。所以并無法操作 DOM \ BOM 等 API。(純數(shù)據(jù)處理)
- 全局對象限制
Worker 全局對象不是
window,所以一些window上的全局屬性和方法也無法訪問。(但可以訪問Navigator和Location接口)
- 通信限制
由于
Worker單獨運行在一個子線程,所以和主線程通信使用發(fā)布、訂閱的消息機制完成。
- 腳本限制
可以在 Worker 中使用
XMLHttpRequest來發(fā)送異步請求。
- 運行環(huán)境限制
Worker 不能運行在
file://協(xié)議下。(不能直接右鍵打開)
API
Worker 的 API 十分簡單,和 iframe 通信 API 類似。
主線程
// 在主線程中,使用 `new Worker` 構(gòu)造函數(shù),來創(chuàng)建一個 `Worker` 實例。
const worker = new Worker('worker.js',{name: 'worker'})
// 主線程中,向子線程 worker 發(fā)送數(shù)據(jù)
worker.postMessage(data)
// 主線程中,監(jiān)聽子線程 worker 回發(fā)的數(shù)據(jù)
worker.onMessage = (data) => {
console.log('來自子線程的數(shù)據(jù)',data)
}
// 主線程中,監(jiān)聽 worker 錯誤
worker.onerror = (err) => {}
// 主線程關(guān)閉 worker 子線程
worker.close()
子線程 worker.js
//在 worker 線程中監(jiān)聽主線程發(fā)送過來的數(shù)據(jù)
self.onmessage = (data) => {
console.log('主線程發(fā)送過來的數(shù)據(jù)',data)
}
// 在 worker 中,向主線程發(fā)送數(shù)據(jù)
self.postMessage(data)
// 在 worker 中監(jiān)聽錯誤
self.onerror = (err) => {}
// 在 worker 中關(guān)閉自己
self.close()
利用 Worker 解決上述主線程阻塞問題
// 在瀏覽器主線程中..
// 定義異步事件,worker 限制耗時操作不會阻塞主線程
document.addEventListener.call(btn, "click", function () {
alert('點我有反應(yīng)嗎?')
})
// 主線程創(chuàng)建 worker
const worker = new Worker('./worker.js', { name: 'my-worker' })
// 主線程給 worker 子線程發(fā)送數(shù)據(jù)
worker.postMessage({ id: 1, name: 'gqs' })
// 主線程注冊子線程的 postmessage 數(shù)據(jù)回調(diào)
worker.onmessage = ({ data }) => {
console.log('來自子線程 worker 的數(shù)據(jù)', data)
}
// 在 worker.js 子線程中
console.log(self.name)
self.onmessage = function ({ data }) {
console.log('接受到了來自主線程的數(shù)據(jù):', data)
sleep(10000) // 耗時 10s
self.postMessage({ id: 1, name: 'gqs' })
}
self.onerror = function (err) {
// worker 線程發(fā)生了錯誤!
throw new Error(err)
}
// 模擬會阻塞主線程的耗時操作.
function sleep (wait = 5000) {
let now = new Date()
while (new Date() - now < wait) { }
}
我們將【會阻塞主線程】的耗時操作,丟在 worker 中去單獨處理后,在查看效果。

總結(jié)
一般情況下,你不會用到
Worker,因為瀏覽器主線程一般很少會出現(xiàn)很復(fù)雜從而導(dǎo)致阻塞的操作。默認(rèn)的耗時操作,都會瀏覽器默認(rèn)處理為宏、微任務(wù)了。
當(dāng)遇到了類似上述那種,在主線程同步環(huán)境下造成阻塞的問題,可以使用
worker來創(chuàng)建一個子線程進行處理,提高用戶體驗。