作者:落課
https://juejin.cn/post/7433719237455937546
前言
本來這篇文章是打算寫我之前那個迭代的首屏優化的,但是我做的那些優化,前幾天上了生產,效果并不如意,因為生產環境不能隨便動,我只能在灰度環境上測試,但是灰度測試后確實是比之前好的。那篇文章我也寫了大半了,過幾天發出來大家指點一下。然后我最近看了評論想著使用webworker
去優化一下我那坨列表,然后我之前也沒用過,最近也研究了一下,所以這篇文章就給大家分享一下webworker怎么用。其實挺簡單的,但是遇到了一些坑!希望大家使用的時候能注意一下。
什么是web worker?
mdn的鏈接 developer.mozilla.org/zh-CN/docs/…[1]
webworker
是html5
的一個api,它的作用就是新開一個線程去做一些操作,因為這個線程并不會阻塞主線程,所以可以去提高網頁的性能。其實可以把他當成一個函數,傳參進行計算得到你想要經過一些代碼處理的東西。
Demo
這里就以計算計算斐波那契數列為例子去給大家演示。
主線程腳本(index.html)
在主線程中 new一個worker對象,傳參的是一個js腳本路徑,不能傳其他類型的文本,也必須是同源的。然后通過message事件和postmessage進行通信即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF - 8">
<title>demo</title>
</head>
<body>
<button onclick="startCalculation()">斐波那契數列</button>
<p id="result"></p>
<script>
function startCalculation() {
// 創建一個Web Worker對象,指定worker.js作為工作線程的腳本
const worker = new Worker('worker.js');
worker.postMessage(20)
// 監聽工作線程發送的消息
worker.onmessage = function (event) {
document.getElementById('result').innerHTML = '斐波那契數列的第20項是:' + event.data;
// 通知關閉線程
worker.postMessage('close')
};
}
</script>
</body>
</html>
工作線程(work.js)
這里主要的點是self,在工作線程中的self
就相當于主線程的window,也就是window中很多東西在self中也能使用。但是工作線程是沒有去獲取dom的權限的,所以他不能也不建議去操作dom。然后就是通過message和postMessage去通信。
// 計算斐波那契數列的函數
function feibo(n) {
if (n === 0 || n === 1) {
return n;
}
return feibo(n - 1) + feibo(n - 2);
}
// 計算斐波那契數列的第10項并發送結果給主線程
self.onmessage = function (e) {
console.log(e.data);
if(e.data == 'close') {
// 關閉線程
self.close()
return
}
const data = feibo(e.data);
self.postMessage(data);
};
方法屬性
self
是對工作線程自身全局對象的引用。類似于瀏覽器主線程中的window
對象,要注意的self
不能訪問 DOM 相關內容
當主線程使用worker.postMessage
發送消息時,工作線程中的message
事件處理函數就會被觸發。當工作線程使用worker.postMessage
發送消息時,主線程中的message
事件處理函數就會被觸發。所以就是用來通信的
用于在工作線程中加載外部腳本。可以同時加載多個腳本,并且腳本會按照它們在importScripts
函數參數中的順序依次加載,不過我感覺這個方法應該很少用。
worker.terminate();
self.colse();
是吧。其實webworker就這么點東西,就是相當一個裝機師傅,你把主板,cpu,顯卡,內存,風扇...給他,師傅就可以還給你一臺主機。然后我在用的時候就發現會有一些坑,就會導致出現bug。
坑1:postMessage
不知道大家知不知道,postMessage是一種異步通信。什么叫異步通信呢。就是我給你通知了,但是我不會去等你執行代碼,我繼續執行我的代碼
。大家可以用上面的demo試一下。我一開始以為是和我們vue的組件自定義事件通信
那樣,是同步通信的。其實應該很多人都不知道這個是同步通信的,這個很重要的,工作中不注意這個時機,就會導致bug,我記得我在面試的時候也被問到過。我下面寫了個vue3的自定義通信的小demo,然后在自定義事件中寫了for循環阻塞5秒鐘,大家可以復制跑一下看看。
parent.vue
<template>
<Child @chageFn="chageFn"></Child>
</template>
<script setup>
import Child from "./child.vue";
const chageFn = () => {
const start = Date.now();
const waitTime = 5000; // 5秒,單位是毫秒
for (let i = 0; Date.now() - start < waitTime; i++) {
}
console.log("5秒時間已過,繼續執行后續代碼");
};
</script>
<style lang="scss" scoped></style>
child.vue
<template>
<button @click="clickFn">點擊我</button>
</template>
<script setup>
import { defineEmits } from "vue";
const emit = defineEmits(["chageFn"]);
const clickFn = () => {
emit("chageFn");
console.log("1");
};
</script>
<style lang="scss" scoped></style>
當我們點擊的時候就可以發現,是在五秒后打印1,并不會立即打印1,但如果是postMessage通信是會立即打印1的。然后這個同步通信并不是說一定會等自定義函數執行完才會走后面的代碼,如果這個自定義函數中是異步代碼,是會先打印1的
,就是正常的事件循環機制,大家也可以試試。
坑2:序列化和反序列化
postMessage在進行通信時會對數據進行序列化的,在message事件接收數據是會反序列的,啥意思呢,就是類似于JSON.parse和JSON.stringify,這兩個api就是將js的數據類型轉化成JSON格式的數據,但是postMessage并不是轉化成JSON格式,它是一種結構化克隆算法,具體我也不清楚。他們的共同點都是不能轉化函數
,JSON.stringify是會將函數變成undefind,postMessage是報錯。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Worker Function and Circular Reference Example</title>
</head>
<body>
<script>
const fn = ()=>{
console.log(1)
}
console.log(JSON.stringify(fn)) // undefind
const worker = new Worker('worker.js')
worker.postMessage(fn) // 報錯
</script>
</body>
</html>
坑3:如何正確的去通信以及正確的去關閉工作線程
當我們使用webworker的時候不可能說每使用一次就new一次webwoker,而是每次都是使用這一個線程進行處理。然后可能會導致什么問題呢?還是那個裝機師傅的例子,有兩個人同時去找這個師傅裝機,裝完了師傅不知道這兩個機子分別是誰的。那師傅肯定沒有這么笨,他肯定會貼個標簽說這個是他的,那個是她的。所以,當我們通信的時候就需要去規定通信的格式
。比如下面這樣,當主線程給工作線程通信的時候,傳一個id字段實現唯一性,當工作線程回復的時候也帶上這個字段,大家具體情況具體分析。
const params = {
id:"1",
params:{
}
}
const response = {
id:"1",
data:{
}
}
關閉工作線程是有兩種方式的,一種是主線程去關閉,另一種是工作線程自己關閉。如果協調不好的話就可能會導致工作線程的代碼并沒有執行完成就關閉
了,然后就會引出很多問題。不過這個我倒是沒遇到過hhh,其實我覺得可以去統一一下關閉的地方。比如,統一由主線程去關閉,當工作線程想關閉的時候,去通過postmessage去通知主線程去關閉。
總結
我覺得webworker其實大部分前端應該是很少用到的,反正我是第一次用。不過也并不是很難,稍微看一下就能學會,就能使用在工作上了。這應該也只會在性能優化的時候去使用,畢竟在大多數情況下封裝函數就行了,誰會想著新開一個線程去處理,麻煩死了。只能說,技多不壓身,學不死就往死里學!
閱讀原文:原文鏈接
該文章在 2025/1/7 11:31:07 編輯過