前言
准备在项目中使用html5新特性Web Worker构建多线程环境,在此做一个简单的入门总结。项目使用的vue版本为 2.6.10,vue-cli版本为 3.12.1,node版本为 12.18.2。
※注:本文代码区域每行开头的“+”表示新增,“-”表示删除,“M”表示修改;代码中的“…”表示省略。
1 概述
JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。随着电脑计算能力的增强,尤其是多核 CPU 的出现,单线程带来很大的不便,无法充分发挥计算机的计算能力。
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务可以交由 Worker 线程执行,主线程(通常负责 UI 交互)能够保持流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(比如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。但是,这也造成了 Worker 比较耗费资源,不应该过度使用,而且一旦使用完毕,就应该关闭。
1.1 使用限制
(1)同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2)DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM 对象,也无法使用document
、window
、parent
这些对象。但是,Worker 线程可以使用navigator
对象和location
对象。
(3)全局对象限制
Worker 的全局对象WorkerGlobalScope
,不同于网页的全局对象Window
,很多接口拿不到。比如,理论上 Worker 线程不能使用console.log
,因为标准里面没有提到 Worker 的全局对象存在console
接口,只定义了Navigator
接口和Location
接口。不过,浏览器实际上支持 Worker 线程使用console.log
,保险的做法还是不使用这个方法。
(4)通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
(5)脚本限制
Worker 线程不能执行alert()
方法和confirm()
方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(6)文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://
),它所加载的脚本,必须来自网络。
2 基本用法
2.1 主线程
浏览器原生提供Worker()
构造函数,用来供主线程生成 Worker 线程。
var worker = new Worker(jsUrl, options)
复制代码
Worker()
构造函数,可以接受两个参数。第一个参数是脚本的网址(必须遵守同源政策),该参数是必需的,且只能加载 JS 脚本,否则会报错。第二个参数是配置对象,该对象可选。它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程。
// 主线程
var worker = new Worker('worker.js', { name : 'myWorker' });
// Worker 线程
self.name // myWorker
复制代码
Worker()
构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会默默地失败。
然后,主线程调用worker.postMessage()
方法,向 Worker 发消息。worker.postMessage()
方法的参数,就是主线程传给 Worker 的数据。它可以是各种数据类型,包括二进制数据。
worker.postMessage({method: 'echo', args: ['Work']})
复制代码
主线程通过worker.onmessage
指定监听函数,接收子线程发回来的消息。通过 event.data 可以获取 Worker 子线程发过来的数据。
worker.onmessage = function (event) {
doSomething(event.data);
}
function doSomething() {
...
}
复制代码
Worker 完成任务以后,主线程就可以把它关掉。
worker.terminate()
复制代码
2.2 Worker 线程
Worker 线程内部需要有一个监听函数,监听message
事件。通过 e.data 可以获取主线程发过来的数据。
self.addEventListener('message', function (e) {
doSomething(e.data)
}, false)
function doSomething() {
...
}
复制代码
上面代码中,self
代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法。
// 写法一
this.addEventListener('message', function (e) {
...
}, false);
// 写法二
addEventListener('message', function (e) {
...
}, false);
复制代码
self.postMessage()
方法用来向主线程发送消息。
self.postMessage(...)
复制代码
Worker 也可以关闭自身
self.close()
复制代码
2.3 Worker 加载脚本
Worker 内部如果要加载其他脚本,有一个专门的方法importScripts()
。
importScripts('script1.js')
复制代码
该方法可以同时加载多个脚本。
importScripts('script1.js', 'script2.js');
复制代码
2.4 错误处理
主线程可以监听 Worker 是否发生错误。如果发生错误,Worker 会触发主线程的error
事件。Worker 内部也可以监听error
事件。
worker.addEventListener('error', function (event) {
console.log(
'ERROR: Line ', event.lineno, ' in ', event.filename, ': ', event.message
)
});
复制代码
3 Vue 项目中的实践
3.1 下载依赖
在 Vue 项目中使用 Web Worker 需要安装 worker-loader
, 我的项目是依赖webpack4的,可以参考这个文档:webpack.docschina.org/loaders/wor…,如果要在 webpack 5 中使用 Web Worker,请查看 webpack.js.org/guides/web-…
安装 worker-loader
cnpm i -D worker-loader
复制代码
vue.config.js 中的配置
module.exports = {
...
configureWebpack: config => {
config.module.rules.push({
test: /\.worker.js$/,
use: {
loader: 'worker-loader'
},
})
},
parallel: false, // 打包报错的配置
...
}
复制代码
注意:这样配置后只有加载 以 .worker.js 结尾的 文件才有效,parallel: false 是解决打包文件时报错问题的。
3.2 项目示例
xxx.vue 组件中的用法
<script>
// 引入子线程运行的js文件
import Worker from 'xxx/js/test.worker.js'
...
mounted() {
this.worker = new Worker()
// 向子线程发送消息
this.worker.postMessage({ method: 'echo', args: ['Work'] })
// 注册监听函数,接收子线程消息
this.worker.onmessage = (event) => {
console.log(event.data)
}
},
beforeDestory() {
// 页面卸载时关闭子线程
this.worker.terminate()
}
...
</script>
复制代码
test.worker.js 中的用法
// 子线程注册监听函数,接收到主线程消息后,将消息回发
self.addEventListener('message', (e) => {
self.postMessage(e.data);
}, false);
// 用定时器发消息
setInterval(() => {
self.postMessage('someMessage');
}, 1000);
...
复制代码
后续项目实践后会更新更加复杂的用法,如有错误,欢迎斧正!
参考资料:
webpack.docschina.org/loaders/wor…