面試講了個虛擬列表,炸了...
Hello,大家好,我是 Sunday
前兩天有位同學面試前端崗位,聊到【虛擬列表】這個功能場景
他提到:我們的項目中,有一個場景會拿到 10 萬條數據。把大量的數據進行渲染時,就需要使用到虛擬列表
但是,面試官輕飄飄的一句話,就把他弄 emo 了
為什么服務端要一口氣給你返回 10 萬條數據???
同時,針對 10 萬條數據,你是怎么解決數據處理時卡頓問題的???
然后,就沒有然后了...
所以今天,我們就來聊聊這個經常被忽略,但關鍵時刻能救命的問題:大量數據處理時,如何避免頁面卡頓?
Web Worker 到底是什么?
我們知道,JavaScript 是單線程的,它的主線程不僅負責執行 JS 邏輯,還要渲染頁面。
也就是說:你在主線程里跑一個重任務,頁面就會卡死。
來看個極簡例子:
for (let i = 0; i < 1e8; i++) {
// 模擬耗時計算
}
document.querySelector(".box").innerText = "程序員Sunday";執行這個代碼時,頁面會直接卡住,直到循環跑完,UI 才會更新。
這就是主線程“爆炸”的典型表現:UI 不響應,操作卡頓,用戶崩潰。
那么我們能不能把這種重活交給別人做?
能!主角登場:Web Worker。
Web Worker 是一種瀏覽器原生提供的 Web API,允許你在 獨立的線程 中運行 JavaScript。
圖片
這意味著我們可以把一些計算密集型的操作(排序、過濾、大數據處理等)扔給 Worker 線程跑,主線程就能繼續負責 UI,不被阻塞。
最重要的一點:Worker 和主線程是并發的、相互獨立的!
而想要使用 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.htmlWeb 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



























