面試講了個虛擬列表,炸了...
Hello,大家好,我是 Sunday
前兩天有位同學(xué)面試前端崗位,聊到【虛擬列表】這個功能場景
他提到:我們的項目中,有一個場景會拿到 10 萬條數(shù)據(jù)。把大量的數(shù)據(jù)進(jìn)行渲染時,就需要使用到虛擬列表
但是,面試官輕飄飄的一句話,就把他弄 emo 了
為什么服務(wù)端要一口氣給你返回 10 萬條數(shù)據(jù)???
同時,針對 10 萬條數(shù)據(jù),你是怎么解決數(shù)據(jù)處理時卡頓問題的???
然后,就沒有然后了...
所以今天,我們就來聊聊這個經(jīng)常被忽略,但關(guān)鍵時刻能救命的問題:大量數(shù)據(jù)處理時,如何避免頁面卡頓?
Web Worker 到底是什么?
我們知道,JavaScript 是單線程的,它的主線程不僅負(fù)責(zé)執(zhí)行 JS 邏輯,還要渲染頁面。
也就是說:你在主線程里跑一個重任務(wù),頁面就會卡死。
來看個極簡例子:
for (let i = 0; i < 1e8; i++) {
// 模擬耗時計算
}
document.querySelector(".box").innerText = "程序員Sunday";執(zhí)行這個代碼時,頁面會直接卡住,直到循環(huán)跑完,UI 才會更新。
這就是主線程“爆炸”的典型表現(xiàn):UI 不響應(yīng),操作卡頓,用戶崩潰。
那么我們能不能把這種重活交給別人做?
能!主角登場:Web Worker。
Web Worker 是一種瀏覽器原生提供的 Web API,允許你在 獨立的線程 中運行 JavaScript。
圖片
這意味著我們可以把一些計算密集型的操作(排序、過濾、大數(shù)據(jù)處理等)扔給 Worker 線程跑,主線程就能繼續(xù)負(fù)責(zé) UI,不被阻塞。
最重要的一點:Worker 和主線程是并發(fā)的、相互獨立的!
而想要使用 Web Worker,那么需要先明確 一個變量、一個構(gòu)造、兩個方法:
變量 self
self 變量是 Web Worker 中的全局上下文對象。它類似于瀏覽器中的 window 對象,但是在 Web Worker 中,window 是不可用的,因為 Worker 是在一個獨立的線程中運行的。
self 代表了 Worker 自己的全局作用域,所有在 Worker 內(nèi)部定義的全局變量和方法都掛載在 self 上。例如:
self.onmessage = function (event) {
console.log("收到主線程消息:", event.data);
};構(gòu)造函數(shù) Worker
創(chuàng)建一個 Web Worker 實例需要使用 Worker 構(gòu)造函數(shù)。該構(gòu)造函數(shù)接受一個字符串參數(shù),指向包含 Worker 代碼的 JavaScript 文件的路徑。這意味著 Worker 代碼和主線程代碼是分開的,Worker 可以獨立執(zhí)行:
const worker = new Worker("worker.js");PS:Web Worker 只能加載與同源策略相同的腳本,因此需要確保 Worker 文件在同一個域下,或者設(shè)置適當(dāng)?shù)?CORS 頭部來允許跨域訪問。
方法一:onmessage
onmessage 是 Worker 中常用的方法之一。它用于接收來自主線程通過 postMessage 發(fā)送的數(shù)據(jù)。
當(dāng)主線程調(diào)用 worker.postMessage() 發(fā)送數(shù)據(jù)時,Worker 會觸發(fā) self.onmessage 事件,并接收數(shù)據(jù)。示例如下:
self.onmessage = function (event) {
console.log("收到來自主線程的消息:", event.data); // 處理數(shù)據(jù)并返回給主線程
self.postMessage("計算完成");
};方法二 postMessage
postMessage 是 Web Worker 與主線程之間通信的核心方法之一。主線程或 Worker 都可以調(diào)用該方法向?qū)Ψ桨l(fā)送數(shù)據(jù)。它必須接受一個參數(shù),該參數(shù)將被傳輸?shù)綄Ψ?。在主線程中,postMessage 的調(diào)用如下:
worker.postMessage('開始計算');在 Worker 中,使用 self.postMessage() 來發(fā)送結(jié)果或數(shù)據(jù)回主線程:
self.postMessage('結(jié)果數(shù)據(jù)');
以下是一段相對完整的實例代碼
// worker.js
self.onmessage = () => {
for (let i = 0; i < 1000000; i++) {
console.log(i);
}
};// index.htmlWeb Worker 的注意事項
對于 Web Worker 來說,我們除了需要知道它的使用方式之外,還有幾個關(guān)鍵的注意事項需要掌握
1. 無法操作 DOM
Web Worker 是在獨立的線程中運行的,而不是在主線程中。因此,它無法直接操作 DOM。
這一點是 Web Worker 的一個重要限制。由于 Web Worker 與主線程之間的環(huán)境是隔離的,無法直接訪問或修改網(wǎng)頁的 DOM 元素。這意味著,你無法通過 Web Worker 來更新頁面內(nèi)容、修改樣式或進(jìn)行 UI 交互。
不過,雖然 Web Worker 無法直接操作 DOM,但我們?nèi)匀豢梢酝ㄟ^ 主線程 和 Web Worker 之間的 消息傳遞機(jī)制 來實現(xiàn)間接的 DOM 操作。
Web Worker 可以通過 postMessage 向主線程發(fā)送數(shù)據(jù),然后由主線程接收數(shù)據(jù)并操作 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) {
// 進(jìn)行一些計算
const result = "計算結(jié)果";
postMessage(result); // 結(jié)果傳遞回主線程
};通過這種方式,Web Worker 可以處理復(fù)雜的計算或數(shù)據(jù)處理任務(wù),然后將結(jié)果通過消息傳遞的方式傳遞回主線程,由主線程來更新 DOM,保持了線程間的良好分離。
2. 錯誤處理(Handle Errors)
Web Worker 是在獨立的線程中運行的,這意味著任何錯誤或異常都不會像在主線程中那樣直接影響用戶界面。因此,對于 Web Worker 的錯誤處理需要特別注意。
常見的錯誤類型
Web Worker 中可能遇到的常見錯誤包括:
- SyntaxError:Worker 文件中的語法錯誤。
- ReferenceError:引用不存在的變量或函數(shù)。
- TypeError:不正確的變量類型使用。
- PostMessage 錯誤:傳遞給
postMessage的對象不符合序列化要求(例如,無法序列化的對象)。
如何捕獲 Worker 錯誤?
Web Worker 提供了 onerror 事件來捕獲 Worker 線程中的錯誤。你可以為 Worker 實例設(shè)置錯誤處理回調(diào)函數(shù)。例如:
worker.onerror = function (event) {
console.error(
"Worker 錯誤:",
event.message,
"在",
event.filename,
"的",
event.lineno,
"行",
);
};這個錯誤處理回調(diào)將捕獲并打印出 Worker 中的錯誤信息,包括錯誤消息、文件名和出錯行號。
Worker 內(nèi)部的錯誤處理
在 Worker 內(nèi)部,你還可以使用 try...catch 來捕獲異常并將錯誤信息傳遞回主線程。例如:
self.onmessage = function (event) {
try {
// 執(zhí)行可能出錯的代碼
let result = someFunction(event.data);
postMessage(result);
} catch (error) {
postMessage("錯誤:" + error.message); // 傳遞錯誤信息回主線程
}
};這樣可以確保 Worker 在執(zhí)行過程中出現(xiàn)異常時不會導(dǎo)致線程崩潰,而是能夠優(yōu)雅地處理錯誤并將信息反饋給主線程。
使用 terminate 和 close
在某些情況下,如果 Web Worker 發(fā)生嚴(yán)重錯誤或者不再需要繼續(xù)執(zhí)行,可以使用 terminate() 方法終止 Worker。在 Worker 內(nèi)部,也可以使用 self.close() 來主動關(guān)閉 Worker。這兩者之間的區(qū)別在于,terminate() 會強(qiáng)制結(jié)束 Worker,且不會觸發(fā)正常的關(guān)閉事件,而 close() 是在 Worker 自己主動結(jié)束時調(diào)用的。
例如,在 Worker 中:
if (someCriticalError) {
self.close(); // 關(guān)閉 Worker
}在主線程中:
worker.terminate(); // 強(qiáng)制終止 Worker



























