一、前言
借用 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.jsmodule.exports = {// other configchainWebpack: (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.dataif (type === 'start') {const res = logic (event.data)self.postMessage({ type: 'finish', data: res })self.close()}}
}