Web Worker
一、概述
众所周知,JavaScript 语言采用的是单线程模型,也就是,所有任务在一个线程上完成,一次只能做一件事。如果前面的任务没有完成,则阻塞后面的任务。极大浪费 CPU 的计算能力↘️。
避免阻塞有两种方法:
- 异步(这里不展开说)
- 多线程(其实也不算是避免阻塞,只是同时可以处理多个任务,减少阻塞的可能)
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker线程,将一些任务分配给子线程运行,主线程和子线程互不干扰。
场景:比如在一些需要密集计算的任务,可以交给 子线程 执行,主线程继续负责 UI 交互,保证页面的流畅性。
注意
- 同源限制,即 Worker 线程的脚本文件必须与主线程 的脚本文件同源。
- 无法访问
DOM
, Worker 线程 无法读取主线程所在网页的 DOM 对象,也就是没有document
、window
、parent
,但有navigator
对象和location
对象。 - 通信限制,必须通过
postMessage()
和 监听onmessage()
来进行通信。 - 不能访问本地文件,即
file://
。
二、基本使用
(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