Skip to content

Web Worker

一、概述

众所周知,JavaScript 语言采用的是单线程模型,也就是,所有任务在一个线程上完成,一次只能做一件事。如果前面的任务没有完成,则阻塞后面的任务。极大浪费 CPU 的计算能力↘️。

避免阻塞有两种方法

  1. 异步(这里不展开说)
  2. 多线程(其实也不算是避免阻塞,只是同时可以处理多个任务,减少阻塞的可能)

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker线程,将一些任务分配给子线程运行,主线程和子线程互不干扰。

场景:比如在一些需要密集计算的任务,可以交给 子线程 执行,主线程继续负责 UI 交互,保证页面的流畅性。

注意

  1. 同源限制,即 Worker 线程的脚本文件必须与主线程 的脚本文件同源。
  2. 无法访问 DOM, Worker 线程 无法读取主线程所在网页的 DOM 对象,也就是没有 documentwindowparent,但有 navigator 对象和 location 对象。
  3. 通信限制,必须通过 postMessage() 和 监听 onmessage() 来进行通信。
  4. 不能访问本地文件,即 file://

二、基本使用

📖 Web Worker MDN 文档

(1)创建 Worker 线程

通过调用 Worker() 构造函数,新建一个 Worker 线程

javascript
// 第一个参数必须是一个脚本文件,第二个参数可选,详情可看文档
const worker = new Worker('myWork.js')

(2)向 Worker 线程发送信息

javascript
// postMessage 可以发送任意类型的数据
worker.postMessage('hello world!')
worker.postMessage({
    eventType: 'sayHi',
    message: 'hellp world!'
})

(3)主线程接收 Worker 线程信息

javascript
worker.onmessage = (event) => {
    // 通过事件对象的 data 属性获取 Worker 发送过来的信息
    console.log('worker message:', event.data)
    // do something
}

(4)销毁 Worker 线程

javascript
// 建议 Worker 完成任务后,关闭 Worker 线程,避免浪费资源。
// 注意:销毁或 Worker 线程主动关闭后,必须需要重新创建使用。
worker.terminate()

(5)Worker 线程接收主线程信息 和 向主线程发送信息

javascript
// 三种写法等价的,self 和 this 都代表子线程的全局对象,可以使用 addEventListener,也可以使用 onmessage。
// Worker 线程也和主线程一样,使用 possMessage() 发送信息。

// 写法一
addEventListener('message', e => {
    // do something
    postMessage('I am a Worker thread')
}, false)

// 写法二
this.addEventListener('message', e => {
    // do something
    this.postMessage('I am a Worker thread')
}, false)

// 写法三
self.addEventListener('message', e => {
    // do something
    self.postMessage('I am a Worker thread')
}, false)

(6)关闭自身 Worker 线程

javascript
// 在 Worker 线程使用。
// 效果与 主线程调用 terminate() 一样,只是一个由主线程发起销毁 Worker 线程,而这里是 Worker 线程主动关闭。
self.close()

(7)Worker 加载脚本

javascript
importScripts('spript1.js'[, ...])

// 除此之外配合 webpack 使用,则可以使用 ES6 import 加载方法。

主要方法

  • Worker.onerror:指定 error 事件的监听函数。
  • Worker.onmessage:指定 message 事件的监听函数,发送过来的数据在Event.data属性中。
  • Worker.onmessageerror:指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时,会触发这个事件。
  • Worker.postMessage():向 Worker 线程发送消息。
  • Worker.terminate():立即终止 Worker 线程。

三、webpack 或 vue 中使用

webpack 4.x 版本下 需要安装 work-loader

详细配置建议查看 work-loader github 的 README,webpack 官网下 loader 文档稍微有点旧了,新版的 work-loader 部分配置属性名更改或删除。

💡

js
// webpack
// webpack.config.js
{
  module: {
    rules: [
      {
        test: /\.worker\.js$/,
        use: { loader: 'worker-loader' }
        options: {
          inline: 'fallback', 
          filename: '[name].[hash].js'
      	}
      }
    ]
  }
}


// vue cli
// vue.config.js
chainWebpack: (config) => {  
  config.module
    .rule('worker')
    .test(/\.worker\.js$/)
    .use('worker-loader')
    .loader('worker-loader')
    .options({
      inline: 'fallback', 
      filename: '[name].[hash].js'
    }).end()

  config.output.globalObject('this') // 注入全局变量
  config.module.rule('js').exclude.add(/\.worker\.js$/) // 修改响应
}

💡使用

js
// where you need it
import worker from 'myWorker.js'

const worker = new worker()
worker.onmessage = function (event) {
  // Received message:I am the message from the thread
  console.log('Received message:' + event.data)
  worker.terminate()
}
worker.postMessage('hello world!')

// myWorker.js
addEventListener('message', function(e) {
 // from main thread:hello world!
 console.log('from main thread:' + e.data)
 // do something
    
 self.postMessage('I am the message from the thread')
}, false)

❗ 在 webpack 5.x 中,可以使用 web workers 来代替 work-loader

js
const worker = new Worker(new URL('./myWorker.js', import.meta.url))
// do something

Released under the MIT License.