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