Web Woeker和Shared Worker的使用以及案例

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、使用须知

  1. 资源耗费:Worker 线程一旦新建成功就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是也造成了 Worker 比较耗费资源,建议使用完毕就关闭。

  2. 同源限制:分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。

  3. DOM 限制:Worker 线程所在的全局对象是 self,它与主线程不一样,无法读取主线程所在网页的 window,DOM,document,parent 等全局对象,但可以读取主线程的:navigator 和 location 对象。

  4. 脚本限制:Web Worker 中可以使用 XMLHttpRequest 和 Axios 发送请求。

  5. 通信联系:Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。

  6. 文件限制: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 允许多个页面共享同一个后台线程,从而实现更高效的资源利用和协同计算。如下,是一个例子,page1page2 共享一个后台线程:

  • 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/,进入调试面板才能看到,步骤如下:

  1. 在 Chrome 浏览器地址栏输入 chrome://inspect,并回车进入
  2. 左边菜单栏,点击 sharedWorker
  3. 右边菜单栏,点击 inspect,即可打开调试面板
调试 Shared Worker

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('._2VdqdF')[0].click(),有惊喜哦

往期文章

个人主页

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容