1、前言
最近做的專案出現了介面卡頓的問題,經過一番排查,發現是因為有個資料做了一些格式化和生成轉換,本來只有 1000 條資料,處理完之後變成了 N 萬條資料(業務需求),導致頁面渲染很慢,甚至會崩潰。於是就想著最佳化一下。初始化的時候不載入,等需要的時候,再使用 Web Worker 來處理資料,避免主執行緒卡頓。
2、介紹 Web Worker
在介紹之前,先說一下Web Worker是為什麼而誕生的。
因為 JavaScript 語言採用的是單執行緒模型,也就是說,所有任務只能在一個執行緒上完成,一次只能做一件事。前面的任務沒做完,後面的任務只能等著。隨著電腦計算能力的增強,尤其是多核 CPU 的出現,單執行緒帶來很大的不便,無法充分發揮計算機的計算能力。
Web Worker 的作用,就是為 JavaScript 創造多執行緒環境。它是 HTML5 標準的一部分,它賦予了開發者利用 JavaScript 操作多執行緒的能力。允許主執行緒建立 Worker 執行緒,將一些任務分配給 Worker 執行緒執行。在主執行緒執行的同時,Worker 執行緒在後臺執行,兩者互不干擾。等到 Worker 執行緒完成計算任務,再把結果返回給主執行緒。這樣的好處是,一些計算密集型或高延遲的任務,被 Worker 執行緒負擔了,主執行緒(通常負責 UI 互動)就會很流暢,不會被阻塞或拖慢。
3、使用須知及相容性
在使用 Worker 前,需要先了解一些規則和瀏覽器的相容性,避免出現一些問題。
3.1、使用須知
-
資源耗費:Worker 執行緒一旦新建成功就會始終執行,不會被主執行緒上的活動(比如使用者點選按鈕、提交表單)打斷。這樣有利於隨時響應主執行緒的通訊。但是也造成了 Worker 比較耗費資源,建議使用完畢就關閉。
-
同源限制:分配給 Worker 執行緒執行的指令碼檔案,必須與主執行緒的指令碼檔案同源。
-
DOM 限制:Worker 執行緒所在的全域性物件是 self,它與主執行緒不一樣,無法讀取主執行緒所在網頁的 window,DOM,document,parent 等全域性物件,但可以讀取主執行緒的:navigator 和 location 物件。
-
指令碼限制:Web Worker 中可以使用 XMLHttpRequest 和 Axios 傳送請求。
-
通訊聯絡:Worker 執行緒和主執行緒不在同一個上下文環境,它們不能直接通訊,必須透過訊息完成。
-
檔案限制:Worker 執行緒中無法讀取本地檔案,即不能開啟本機的檔案系統(file://),它所載入的指令碼,必須來自網路。
3.2、相容性
瀏覽器 | 相容性 | 最低相容版本 |
---|---|---|
Chrome | 完全相容 | 4.0 (2008 年) |
Firefox | 完全相容 | 3.5 (2009 年) |
Safari | 完全相容 | 3.1 (2007 年) |
Edge | 完全相容 | 79 (2020 年) |
IE | 部分相容 | 10 (2012 年) |
Opera | 完全相容 | 10.5 (2010 年) |
4、使用 Web Worker
直接使用 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 值,主要用於除錯目的。 |
4.1、建立 Web Worker
主執行緒:
const myWorker = new Worker('/worker.js')
// 接收訊息
myWorker.addEventListener('message', (e) => {
console.log(e.data)
})
// 向 worker 執行緒傳送訊息
myWorker.postMessage('Greeting from Main.js')
4.2、與主執行緒通訊
worker 執行緒:
// 接收到訊息
self.addEventListener('message', (e) => {
console.log(e.data)
})
// 一頓計算後 傳送訊息
const calculateDataFn = () => {
self.postMessage('ok')
}
4.3、終止 Web Worker
兩個執行緒裡都可以操作,自由選擇。
- 在主執行緒中操作:
// 建立worker
const myWorker = new Worker('/worker.js')
// 關閉worker
myWorker.terminate()
- 在 worker 執行緒中操作:
self.close()
4.4、監聽錯誤資訊
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)
})
- 在 worker 執行緒:
self.addEventListener('error', (err) => {
console.log(err.message)
})
self.addEventListener('messageerror', (err) => {
console.log(err.message)
})
5、使用 Shared Worker
SharedWorker 允許多個頁面共享同一個後臺執行緒,從而實現更高效的資源利用和協同計算。如下,是一個例子,page1
和 page2
共享一個後臺執行緒:
- sharedWorker.js
/**
* @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}`))
}
}
- sharedWorkerHook.js
const sharedWorker = new SharedWorker(new URL('../../utils/webworker.js', import.meta.url), 'test')
export default sharedWorker
- page1
<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>
- page2
<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>
4.5、除錯 Shared Worker
sharedWorker 中除錯,使用 console 列印資訊,不會出現在主執行緒的的控制檯中,需要在 Chrome 瀏覽器位址列輸入 chrome://inspect/,進入除錯皮膚才能看到,步驟如下:
- 在 Chrome 瀏覽器位址列輸入 chrome://inspect,並回車進入
- 左邊選單欄,點選 sharedWorker
- 右邊選單欄,點選 inspect,即可開啟除錯皮膚
6、使用中的一些坑
在使用中,雖然查閱了一些文件和部落格,但是還是出現了以下問題,記錄一下。
6.1、Web Woeker 中引入了其餘檔案
有一些場景,需要放到 worker 程式去處理的任務很複雜,就會引入其餘檔案,這時候,可以在worker執行緒中利用importScripts()方法載入我們需要的js檔案
importScripts('./utils.js')
如果引入的是ESModule模式,需要在初始化的時候,指定type的模式。
const worker = new Worker('/worker.js', { type: 'module' })
6.2、在 WebPack 或 Vite 中使用
在webpack和vite中使用,步驟如下:
6.2.1、webpack中使用
第一步:安裝外掛: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'
})
5.2.2、vite中使用
第一步:安裝外掛: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'
})
5.3、sharedWorker的引入問題
在使用sharedWorker的過程中,發現sharedWorker程式裡,始終只有一個例項,但是我確實在幾個頁面都初始化了同一個sharedWorker的js檔案,最後除錯發現,原因是透過外掛引入之後名字變了,一個是xxxx0.js,一個是xxxx1.js,所以導致我每次初始化的時候,都不認為是同一個例項,所以訊息無法同步。
- 解決辦法:如[使用 Shared Worker](#5-使用 Shared Worker)裡的例子一樣,專門用一個檔案new好這個sharedWorker,然後匯出,在需要的頁面匯入後再執行即可。
如
7、後語
在本文中,我們學習了Web Worker的作用和使用方法,以及如何在Vue中使用Web Worker,最後,我們還學習了Shared Worker的使用方法。
其實webworker家族裡還有一位更加強大的成員,那就是Service Worker。它可以做的東西很多,比如攔截全域性的fetch事件、快取靜態資源/離線快取用於首屏載入、後臺同步,訊息推送等等,奈何篇幅有限,下回再做講解。
本次分享就到這兒啦,我是鵬多多,如果您看了覺得有幫助,歡迎評論,關注,點贊,轉發,我們下次見~
PS:在本頁按F12,在console中輸入document.querySelectorAll('.diggit')[0].click(),有驚喜哦
公眾號
往期文章
- Vue2全家桶+Element搭建的PC端線上音樂網站
- 超詳細的Cookie增刪改查
- 助你上手Vue3全家桶之Vue-Router4教程
- 助你上手Vue3全家桶之Vue3教程
- 助你上手Vue3全家桶之VueX4教程
- 使用nvm管理node.js版本以及更換npm淘寶映象源
- 超詳細!Vue-Router手把手教程
- 超詳細!Vue的九種通訊方式
- 超詳細!Vuex手把手教程
個人主頁