最近做的專案出現了介面卡頓的問題,經過一番排查,發現是因為有個資料做了一些格式化和生成轉換,本來只有 1000 條資料,處理完之後變成了 N 萬條資料(業務需求),導致頁面渲染很慢,甚至會崩潰。於是就想著優化一下。初始化的時候不載入,等需要的時候,再使用 Web Worker 來處理資料,避免主執行緒卡頓。
在介紹之前,先說一下Web Worker是為什麼而誕生的。
因為 JavaScript 語言採用的是單執行緒模型,也就是說,所有任務只能在一個執行緒上完成,一次只能做一件事。前面的任務沒做完,後面的任務只能等著。隨著電腦計算能力的增強,尤其是多核 CPU 的出現,單執行緒帶來很大的不便,無法充分發揮計算機的計算能力。
Web Worker 的作用,就是為 JavaScript 創造多執行緒環境。它是 HTML5 標準的一部分,它賦予了開發者利用 JavaScript 操作多執行緒的能力。允許主執行緒建立 Worker 執行緒,將一些任務分配給 Worker 執行緒執行。在主執行緒執行的同時,Worker 執行緒在後臺執行,兩者互不干擾。等到 Worker 執行緒完成計算任務,再把結果返回給主執行緒。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 執行緒負擔了,主執行緒(通常負責 UI 互動)就會很流暢,不會被阻塞或拖慢。
在使用 Worker 前,需要先了解一些規則和瀏覽器的相容性,避免出現一些問題。
資源耗費:Worker 執行緒一旦新建成功就會始終執行,不會被主執行緒上的活動(比如使用者點選按鈕、提交表單)打斷。這樣有利於隨時響應主執行緒的通訊。但是也造成了 Worker 比較耗費資源,建議使用完畢就關閉。
同源限制:分配給 Worker 執行緒執行的指令碼檔案,必須與主執行緒的指令碼檔案同源。
DOM 限制:Worker 執行緒所在的全域性物件是 self,它與主執行緒不一樣,無法讀取主執行緒所在網頁的 window,DOM,document,parent 等全域性物件,但可以讀取主執行緒的:navigator 和 location 物件。
指令碼限制:Web Worker 中可以使用 XMLHttpRequest 和 Axios 傳送請求。
通訊聯絡:Worker 執行緒和主執行緒不在同一個上下文環境,它們不能直接通訊,必須通過訊息完成。
檔案限制:Worker 執行緒中無法讀取本地檔案,即不能開啟本機的檔案系統(file://),它所載入的指令碼,必須來自網路。
瀏覽器 | 相容性 | 最低相容版本 |
---|---|---|
Chrome | 完全相容 | 4.0 (2008 年) |
Firefox | 完全相容 | 3.5 (2009 年) |
Safari | 完全相容 | 3.1 (2007 年) |
Edge | 完全相容 | 79 (2020 年) |
IE | 部分相容 | 10 (2012 年) |
Opera | 完全相容 | 10.5 (2010 年) |
直接使用 JavaScript 原生的 Worker()建構函式,它的引數如下:
引數 | 說明 |
---|---|
path | 有效的 js 指令碼的地址,必須遵守同源策略 |
options.type | 可選。用以指定 worker 型別。該值可以是 classic 或 module,預設 classic |
options.credentials | 可選。指定 worker 憑證。該值可以是 omit, same-origin,或 include。如果未指定,或者 type 是 classic,將使用預設值 omit (不要求憑證) |
options.name | 可選。在 DedicatedWorkerGlobalScope 的情況下,用來表示 worker 的 scope 的一個 DOMString 值,主要用於偵錯目的。 |
主執行緒:
const myWorker = new Worker('/worker.js')
// 接收訊息
myWorker.addEventListener('message', (e) => {
console.log(e.data)
})
// 向 worker 執行緒傳送訊息
myWorker.postMessage('Greeting from Main.js')
worker 執行緒:
// 接收到訊息
self.addEventListener('message', (e) => {
console.log(e.data)
})
// 一頓計算後 傳送訊息
const calculateDataFn = () => {
self.postMessage('ok')
}
兩個執行緒裡都可以操作,自由選擇。
// 建立worker
const myWorker = new Worker('/worker.js')
// 關閉worker
myWorker.terminate()
self.close()
Web Worker 提供了兩個事件監聽錯誤回撥,error 和 messageerror。
事件 | 描述 |
---|---|
error | 當 worker 內部出現錯誤時觸發 |
messageerror | 當 message 事件接收到無法被反序列化的引數時觸發 |
// 建立worker
const myWorker = new Worker('/worker.js')
myWorker.addEventListener('error', (err) => {
console.log(err.message)
})
myWorker.addEventListener('messageerror', (err) => {
console.log(err.message)
})
self.addEventListener('error', (err) => {
console.log(err.message)
})
self.addEventListener('messageerror', (err) => {
console.log(err.message)
})
SharedWorker 允許多個頁面共用同一個後臺執行緒,從而實現更高效的資源利用和協同計算。如下,是一個例子,page1
和 page2
共用一個後臺執行緒:
/**
* @description 所有連線這個worker的集合
*/
const portsList = []
/**
* @description 連線成功回撥
*/
self.onconnect = (event) => {
// 當前觸發連線的埠
const port = event.ports[0]
// 新增進去
portsList.push(port)
// 接收到訊息的回撥
port.onmessage = (event) => {
// 獲取傳遞的訊息
const { message, value } = event.data
// 計算
let result = 0
switch (message) {
case 'add':
result = value * 2
break
case 'multiply':
result = value * value
break
default:
result = value
}
// 給所有連線的目標傳送訊息
portsList.forEach((port) => port.postMessage(`${message}結果是:${result}`))
}
}
const sharedWorker = new SharedWorker(new URL('../../utils/webworker.js', import.meta.url), 'test')
export default sharedWorker
<template>
<div @click="sendMessage">點選1</div>
</template>
<script>
import sharedWorkerHook from './sharedWorkerHook'
export default {
name: '',
data() {
return {}
},
computed: {},
created() {},
mounted() {
sharedWorkerHook.port.start()
// 接收SharedWorker返回的結果
sharedWorkerHook.port.onmessage = event => {
console.log(event.data)
}
},
methods: {
sendMessage() {
sharedWorkerHook.port.postMessage({ message: 'add', value: 1 })
}
}
}
</script>
<template>
<div @click="sendMessage">點選2</div>
</template>
<script>
import sharedWorkerHook from './sharedWorkerHook'
export default {
name: '',
data() {
return {}
},
computed: {},
created() {},
mounted() {
sharedWorkerHook.port.start()
// 接收SharedWorker返回的結果
sharedWorkerHook.port.onmessage = event => {
console.log(event.data)
}
},
methods: {
sendMessage() {
sharedWorkerHook.port.postMessage({ message: 'multiply', value: 1 })
}
}
}
</script>
sharedWorker 中偵錯,使用 console 列印資訊,不會出現在主執行緒的的控制檯中,需要在 Chrome 瀏覽器位址列輸入 chrome://inspect/,進入偵錯面板才能看到,步驟如下:
在使用中,雖然查閱了一些檔案和部落格,但是還是出現了以下問題,記錄一下。
有一些場景,需要放到 worker 程序去處理的任務很複雜,就會引入其餘檔案,這時候,可以在worker執行緒中利用importScripts()方法載入我們需要的js檔案
importScripts('./utils.js')
如果引入的是ESModule模式,需要在初始化的時候,指定type的模式。
const worker = new Worker('/worker.js', { type: 'module' })
在webpack和vite中使用,步驟如下:
第一步:安裝外掛:worker-plugin
npm install worker-plugin -D
第二步:在vue.config.js的configureWebpack.plugins中設定
const WorkerPlugin = require('worker-plugin')
module.exports = {
outputDir: 'dist',
// 其餘設定......
configureWebpack: {
devServer: {
open: false,
host: 'localhost',
// 其餘設定......
},
plugins: [
// 其餘設定......
new WorkerPlugin()
]
}
}
第三步:使用
const webWorker = new Worker(new URL('../utils/worker.js', import.meta.url), {
type: 'module'
})
第一步:安裝外掛:worker-plugin
npm install worker-plugin -D
第二步:在vite.config.js的plugins中設定
import worker from 'worker-plugin'
const viteConfig = defineConfig((mode: ConfigEnv) => {
return {
plugins: [vue(), worker()],
server: {
host: '0.0.0.0',
port: 7001,
open: true
}
}
})
export default viteConfig
第三步:使用
const webWorker = new Worker(new URL('../utils/worker.ts', import.meta.url), {
type: 'module'
})
在使用sharedWorker的過程中,發現sharedWorker程序裡,始終只有一個範例,但是我確實在幾個頁面都初始化了同一個sharedWorker的js檔案,最後偵錯發現,原因是通過外掛引入之後名字變了,一個是xxxx0.js,一個是xxxx1.js,所以導致我每次初始化的時候,都不認為是同一個範例,所以訊息無法同步。
如
在本文中,我們學習了Web Worker的作用和使用方法,以及如何在Vue中使用Web Worker,最後,我們還學習了Shared Worker的使用方法。
其實webworker家族裡還有一位更加強大的成員,那就是Service Worker。它可以做的東西很多,比如攔截全域性的fetch事件、快取靜態資源/離線快取用於首屏載入、後臺同步,訊息推播等等,奈何篇幅有限,下回再做講解。
本次分享就到這兒啦,我是鵬多多,如果您看了覺得有幫助,歡迎評論,關注,點贊,轉發,我們下次見~
PS:在本頁按F12,在console中輸入document.querySelectorAll('.diggit')[0].click(),有驚喜哦
公眾號
往期文章
個人主頁