一、前言
借用 MDN 的描述,Web Worker 是在后台线程中执行脚本的简单方法,可以在不阻塞用户界面的情况下执行任务。
在 worker 中执行的代码不用担心阻塞用户界面,可以放心的执行一些计算密集型的任务。但是也不能过度使用,并且应该在任务结束后及时关闭 worker,减少系统资源消耗。
由于 worker 运行在与主线程不同的上下文中,所以在编写 worker 中执行的代码时有一些要注意的差异:
没有 window 对象,worker 中的全局对象 是 self self 中没有 document 对象,也没有 alert() confirm() 等方法,但 navigator location XMLHttpRequest 等对象以及 setTimeout() setInterval() 等方法是可以正常使用的。
无法与主线程直接通信 只能通过 postMessage 和 onmessage 这两个方法发送/接收消息,允许传递对象,但是以深拷贝的形式传递。
共享 worker 的使用上与专用 worker 有所差异,主要区别是共享 worker 通信时需指定端口号,并且允许被多个 窗口/iframe/worker 访问。具体可查看 MDN 介绍,我只使用了专用 worker 。
此外,也许基于安全等考量,web worker 的使用还有另外的一些限制:
同源限制 worker 中执行的代码必须放到脚本文件中,且该脚本文件的拉取必须遵守同源策略。
DOM 限制 worker 中不能读取和操作 DOM 对象。
二、使用
2.1 示例
// 主线程
const worker = new Worker('test.worker.js');
worker.postMessage('start', {data: {});
worker.onmessage = function(event) {
const {type, data} = event;
if (type === 'end') {
worker.postMessage({type: 'end', data: ''})
}
}
// public/test.woker.js
console.log('start worker selg:', self);
self.onmessage = function(event) {
const {type, data} = event.data;
if (type === 'start') {
....
self.postMessage({type: 'finish', data: ''});
self.close(); // 关闭 worker
}
}
主线程和 worker 双方的收发消息方式都是完全一样的。通过 event.data 可以拿到对方传递的消息,具体的消息结构由我们自己定义。
2.2 终止 worker
// 可以直接调用 terminate 方法强制结束 worker 线程
worker.terminate(); // 强行终止 worker
// 可以通过消息来告知 worker 主动退出
// 主线程
worker.postMessage('stop');
// public/test.worker.js
self.onmessage = function(event) {
const {type} = event.data;
if (type === 'stop') {
...
self.close(); // 关闭 worker
}
}
2.3 异常处理
// public/test.worker.js
self.onerror = function (event) {
thisWorker.postMessage({ type: 'error', event })
}
// 主线程
const worker = new Worker('test.worker.js');
worker.onmessage = function(event) {
if (event.data.type === 'error') {
...
}
}
或
worker.onerror= function(event) {
if (event.data.type === 'error') {
...
}
}
2.4 加载脚本
importScripts(); /* 什么都不引入 */
importScripts("test.js"); /* 只引入 "test.js" */
importScripts("//example.com/hello.js"); /* 其他来源导入脚本 */
// importScripts 是同步的,所有脚本拉取完毕后才会返回
三、worker-loader
importScripts 只能加载网络文件,无法拉取源代码。woker.js 文件必须放到 /public 文件夹下(或其他可通过同源策略加载的网络地址中)。这意味着没办法复用源码中的一些公共逻辑。
webpack4,要解决这个问题,在 worker 中方便的复用公共逻辑,需要使用 worker-loader 插件。
webpack5 已经原生支持引用源码,不再需要 worker-loader。
new Worker(new URL('./worker.js', import.meta.url));
3.1 安装
yarn add -D worker-loader
3.2 配置vue.config.js
// vue.config.js
module.exports = {
// other config
chainWebpack: (config) => {
// other config
/** web worker 支持 */
config.module
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.end()
}
}
3.3 使用
// test.vue
<script>
import testWorker from '@/common/test.worker.js'
export default {
...
methods: {
startWorker() {
const worker = new testWorker ()
worker.postMessage('start', {data: {});
worker.onmessage = function(event) {
...
}
}
}
}
</script>
// src/common/test.worker.js
import logic from './logic '
if (self) {
self.onerror = function (event) {
self.postMessage({ type: 'error', event })
self.close()
}
self.onmessage = function (event) {
const { type } = event.data
if (type === 'start') {
const res = logic (event.data)
self.postMessage({ type: 'finish', data: res })
self.close()
}
}
}