面試必備:WebWorker 處理復雜場景下卡頓問題!
Hello,大家好,我是 Sunday
這段時間有不少同學在面試中遇到了 web worker 的問題。所以今天咱們就通過這篇文章,把 web worker 搞明白!
Web Worker 到底是什么?
因為 JS 是單線程(主線程)的,這意味著它一次只能做一件事。
當你在瀏覽器中執行大量的計算任務(如數據處理、復雜算法等)時,主線程會被阻塞,導致頁面無法響應用戶交互,影響用戶體驗。換句話說,當主線程上執行耗時任務時,UI 會被卡死,用戶無法進行正常的操作。
例如,假設我們要執行一個耗時的任務,比如對一個非常大的數據集合進行排序或計算。在主線程上執行這類任務時,頁面會變得不響應,直到任務完成后,UI 才會刷新。
<div class="box">hello</div>
for (let i = 0; i < 1000000; i++) {
console.log(i)
}
document.querySelector('.box').innerHTML = '程序員Sunday'
而 web worker 就可以解決掉卡頓的問題。
圖片
Web Worker 是一種 Web API,它提供了在 后臺線程 中運行 JavaScript 的能力。這使得我們可以將耗時的計算任務移至后臺線程,避免了主線程被阻塞,從而提升應用性能。
想要使用 Web Worker,那么需要先明確 一個變量、一個構造、兩個方法:
變量 self
self 變量是 Web Worker 中的全局上下文對象。它類似于瀏覽器中的 window 對象,但是在 Web Worker 中,window 是不可用的,因為 Worker 是在一個獨立的線程中運行的。
self 代表了 Worker 自己的全局作用域,所有在 Worker 內部定義的全局變量和方法都掛載在 self 上。例如:
self.onmessage = function(event) {
console.log('收到主線程消息:', event.data);
};
構造函數 Worker
創建一個 Web Worker 實例需要使用 Worker 構造函數。該構造函數接受一個字符串參數,指向包含 Worker 代碼的 JavaScript 文件的路徑。這意味著 Worker 代碼和主線程代碼是分開的,Worker 可以獨立執行:
const worker = new Worker('worker.js');
PS:Web Worker 只能加載與同源策略相同的腳本,因此需要確保 Worker 文件在同一個域下,或者設置適當的 CORS 頭部來允許跨域訪問。
方法一:onmessage
onmessage 是 Worker 中常用的方法之一。它用于接收來自主線程通過 postMessage 發送的數據。
當主線程調用 worker.postMessage() 發送數據時,Worker 會觸發 self.onmessage 事件,并接收數據。示例如下:
self.onmessage = function(event) {
console.log('收到來自主線程的消息:', event.data);
// 處理數據并返回給主線程
self.postMessage('計算完成');
};
方法二 postMessage
postMessage 是 Web Worker 與主線程之間通信的核心方法之一。主線程或 Worker 都可以調用該方法向對方發送數據。它必須接受一個參數,該參數將被傳輸到對方。在主線程中,postMessage 的調用如下:
worker.postMessage('開始計算');
在 Worker 中,使用 self.postMessage() 來發送結果或數據回主線程:
self.postMessage('結果數據');
以下是一段相對完整的實例代碼
// worker.js
self.onmessage = () => {
for (let i = 0; i < 1000000; i++) {
console.log(i)
}
}
// index.html
<script>
const worker = new Worker('./worker.js')
worker.postMessage(1)
document.querySelector('.box').innerHTML = '程序員Sunday'
</script>
Web Worker 的注意事項
對于 Web Worker 來說,我們除了需要知道它的使用方式之外,還有幾個關鍵的注意事項需要掌握
1. 無法操作 DOM
Web Worker 是在獨立的線程中運行的,而不是在主線程中。因此,它無法直接操作 DOM。
這一點是 Web Worker 的一個重要限制。由于 Web Worker 與主線程之間的環境是隔離的,無法直接訪問或修改網頁的 DOM 元素。這意味著,你無法通過 Web Worker 來更新頁面內容、修改樣式或進行 UI 交互。
不過,雖然 Web Worker 無法直接操作 DOM,但我們仍然可以通過 主線程 和 Web Worker 之間的 消息傳遞機制 來實現間接的 DOM 操作。
Web Worker 可以通過 postMessage 向主線程發送數據,然后由主線程接收數據并操作 DOM。例如:
// 主線程
const worker = new Worker('worker.js');
worker.onmessage = function(event) {
// 在這里操作 DOM
document.getElementById('result').textContent = event.data;
};
worker.postMessage('開始計算');
// Worker 中的代碼
self.onmessage = function(event) {
// 進行一些計算
const result = '計算結果';
postMessage(result); // 結果傳遞回主線程
};
通過這種方式,Web Worker 可以處理復雜的計算或數據處理任務,然后將結果通過消息傳遞的方式傳遞回主線程,由主線程來更新 DOM,保持了線程間的良好分離。
2. 錯誤處理(Handle Errors)
Web Worker 是在獨立的線程中運行的,這意味著任何錯誤或異常都不會像在主線程中那樣直接影響用戶界面。因此,對于 Web Worker 的錯誤處理需要特別注意。
常見的錯誤類型
Web Worker 中可能遇到的常見錯誤包括:
- SyntaxError:Worker 文件中的語法錯誤。
- ReferenceError:引用不存在的變量或函數。
- TypeError:不正確的變量類型使用。
- PostMessage 錯誤:傳遞給 postMessage 的對象不符合序列化要求(例如,無法序列化的對象)。
如何捕獲 Worker 錯誤?
Web Worker 提供了 onerror 事件來捕獲 Worker 線程中的錯誤。你可以為 Worker 實例設置錯誤處理回調函數。例如:
worker.onerror = function(event) {
console.error('Worker 錯誤:', event.message, '在', event.filename, '的', event.lineno, '行');
};
這個錯誤處理回調將捕獲并打印出 Worker 中的錯誤信息,包括錯誤消息、文件名和出錯行號。
Worker 內部的錯誤處理
在 Worker 內部,你還可以使用 try...catch 來捕獲異常并將錯誤信息傳遞回主線程。例如:
self.onmessage = function(event) {
try {
// 執行可能出錯的代碼
let result = someFunction(event.data);
postMessage(result);
} catch (error) {
postMessage('錯誤:' + error.message); // 傳遞錯誤信息回主線程
}
};
這樣可以確保 Worker 在執行過程中出現異常時不會導致線程崩潰,而是能夠優雅地處理錯誤并將信息反饋給主線程。
使用 terminate 和 close
在某些情況下,如果 Web Worker 發生嚴重錯誤或者不再需要繼續執行,可以使用 terminate() 方法終止 Worker。在 Worker 內部,也可以使用 self.close() 來主動關閉 Worker。這兩者之間的區別在于,terminate() 會強制結束 Worker,且不會觸發正常的關閉事件,而 close() 是在 Worker 自己主動結束時調用的。
例如,在 Worker 中:
if (someCriticalError) {
self.close(); // 關閉 Worker
}
在主線程中:
worker.terminate(); // 強制終止 Worker