複雜運算就交給web worker吧!

近期在工作上遇到了在網頁進行複雜運算而導致 UI 畫面卡頓的議題,便開始著手研究 web worker,由於 javascript 是單執行緒,一次僅能處理一件事情,有了 web worker 就能夠在背景執行緒處理其他的運算,在執行複雜運算的時候,使用 web worker 可有效避免畫面凍結、卡頓,讓網頁在執行大量運算時使用者依舊可以流暢地操作網頁的介面,但要特別留意的是 web worker 無法直接操作 DOM 元素

web worker 使用流程

  • 建立 web worker
  • 主執行緒使用 postMessage 來和 web worker 溝通
  • web worker 使用 onMessage 來接收事件
  • web worker 收到來自主執行緒的請求,執行複雜運算
  • web worker 運算完畢後,將結果回傳給主執行緒

web worker 是 javaScript 原生提供的方法,不需額外安裝套件,也不侷限於任何一種前端框架使用,各家瀏覽器支援度非常的高!

動手實作

畫面上會有三個按鈕,第一個按鈕 start 會觸發複雜的運算

test1 和 test2 按鈕則是用來模擬網頁頁面上的其他可互動的 ui 元件,這兩個按鈕會在點選的時候,console log 當前的 timeStamp

先建立好 worker.js,裡面有一個模擬複雜運算的函式,當接受到外部傳來的訊息 action 為 calc 就開始執行複雜運算,運算完成之後再將結果用 postMessage 的方式傳遞給主執行緒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//worker.js

//模擬複雜的運算
const complexCalculation = () => {
let result = 0;
for (let i = 0; i < 10000000000; i++) {
result += i;
}

return result;
};

//接收外界訊息
addEventListener("message", (event: MessageEvent) => {
console.log("event", event);
if (event.data.action === "calc") {
const result = complexCalculation();
self.postMessage(result);
}
});

然後在 html 檔案使用 web worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<body>
<button id="start">start</button>
<button id="test1">test1</button>
<button id="test2">test2</button>
<h1></h1>
</body>

<script>
document.getElementById("start").addEventListener("click", () => {
// 建立 web worker
const worker = new Worker("./worker.js");

// 用onmessage監聽 web worker postMessage的內容
worker.onmessage = (res) => {
//將計算過後的結果塞回h1
document.getElementsByTagName(
"h1"
)[0].textContent = `result is ${res.data}`;
};

// 向web work發送action為calc的message
worker.postMessage({ action: "calc" });
});
</script>

按下 start 之後,開始進行複雜的運算,此時點選 test1 和 test2 的按鈕會即時
console log 當下的 timeStamp,意謂著使用者依然可以正常的操作介面
以下為對照組,不建立 web worker,按下
start 直接執行複雜的運算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<button id="start">start</button>
<button id="test1">test1</button>
<button id="test2">test2</button>
<h1></h1>
</body>

<script>
const complexCalculation = () => {
let result = 0;
for (let i = 0; i < 10000000000; i++) {
result += i;
}

return result;
};

document.getElementById("start").addEventListener("click", () => {
const res = complexCalculation();
document.getElementsByTagName("h1")[0].textContent = `result is ${res}`;
});
</script>

<script>
document.getElementById("test1").addEventListener("click", () => {
console.log("test1" + Date.now());
});

document.getElementById("test2").addEventListener("click", () => {
console.log("test2" + Date.now());
});
</script>

按下 start 後,再去按其他兩個按鈕會完全沒有反應,必須等到計算完成之後,才會 console log 剛剛的操作結果,這對使用者來說就會是一個相當糟糕的操作體驗

結語

現代對於網頁的使用者體驗越來越重視,除了介面易用,更重要的是能不能夠流暢的操作,所以當遇到複雜運算而導致網頁卡頓的問題時,不妨試著使用 web worker 來做優化吧!