一文了解Web Worker
Posted xiangzhihong8
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文了解Web Worker相关的知识,希望对你有一定的参考价值。
一、概述
众所周知,javascript最初设计是运行在浏览器中的,为了防止多个线程同时操作DOM带来的渲染冲突问题,所以JavaScript执行器被设计成单线程。但是随着前端技术的发展,JavaScript要处理的工作也越来越复杂,当我们遇到需要大量计算的场景时,比如图像处理、视频解码、耗时计算等场景时,JavaScript的主线程就会被长时间阻塞,甚至造成页面卡顿,影响用户体验。
为了实现异步编程,JavaScript先后出现了AJAX、Promises、async/await 等技术,当然,本文要讲的Web Worker技术也可以实现异步编程。目前,主流的浏览器都支持Web Worker。
二、Web Worker简介
Web Worker是 html5 标准的一部分,这一规范定义了一套 API,允许开发者在 JavaScript 主线程之外开辟新的 Worker 线程,并允许将一段 JavaScript 脚本运行其中。Web Worker的出现,赋予了开发者操作多线程的能力。
因为是独立的线程,Worker 线程与JavaScript主线程是能够同时运行的,且互不阻塞。所以,在应对需要异步操作的场景时,我们可以把运算任务交给 Worker 线程去处理,当 Worker 线程计算完成,再把结果返回给 JavaScript 主线程。这样,JavaScript 主线程只用专注处理业务逻辑,不用耗费过多时间去处理大量复杂计算,从而实现异步编程效果,提升开发和运行效率。
事实上,虽然 Worker 线程是在浏览器环境中被唤起,但是它与当前页面窗口运行在不同的全局上下文中,我们常用的顶层对象 window,以及 parent 对象在 Worker 线程上下文中是不可用的。另外,在 Worker 线程上下文中,操作 DOM 的行为也是不可行的,document对象也不存在。但是,location和navigator对象可以以可读方式访问。除此之外,绝大多数 Window 对象上的方法和属性,都被共享到 Worker 上下文全局对象 WorkerGlobalScope 中。同样,Worker 线程上下文也存在一个顶级对象 self。
三、基本使用
3.1 检查是否支持Web Worker
在创建 web worker 之前,请检查用户的浏览器是否支持它,示例代码如下。
if (typeof(Worker) !== "undefined")
// Yes! Web worker support!
else
// Sorry! No Web Worker support..
3.2 创建Work
创建worker只需要通过new方式调用 Worker() 构造函数即可,它接收两个参数:
const worker = new Worker(path, options);
当然,不传options也是可以的。其中,path和options含义如下:
- path:有效的js脚本的地址,必须遵守同源策略。
- options.type:可选,用以指定 worker 类型,可选值为classic、module。
- options.credentials:可选,指定 worker 凭证,可选值omit、same-origin和nclude
- options.name:可选,在 DedicatedWorkerGlobalScope 的情况下,用来表示 worker 的 scope 的一个 DOMString 值,主要用于调试目的。
3.3 数据传递
主线程与Worker线程都可以通过 postMessage 方法来发送消息,然后使用监听 message 事件来接收消息。
main.js
const myWorker = new Worker('/worker.js');
//接受消息
myWorker.addEventListener('message', e =>
console.log(e.data);
);
//发送消息
myWorker.postMessage('Greeting from Main.js');
而使用postMessage() 方法传递的数据类型可以是字符串、对象、数组等。
3.4 监听错误信息
对于监听message事件的错误信息,Web Worker提供两个事件来监听错误,分别error 和 messageerror。这两个事件的区别是:
- error:当worker内部出现错误时触发。
- messageerror:当 message 事件接收到无法被反序列化的参数时触发。
当然,监听错误信息的调用方式是一样的。
const myWorker = new Worker('/worker.js');
myWorker.addEventListener('error', err =>
console.log(err.message);
);
myWorker.addEventListener('messageerror', err =>
console.log(err.message)
);
3.5 关闭Web Worker
当Web Worker对象被创建时,它就开启了消息监听直到它被终止。所以,为了避免造成资源浪费,需要在使用结束之后放浏览器/计算机资源。不过,在主线程和在Worker线程的关闭方式是不一样的。如果在主线程,调用terminate()方法关闭即可:
const myWorker = new Worker('/worker.js');
myWorker.terminate();
如果是在Work线程则可以调用close()方法进行关闭:
self.close();
需要说明的是,无论是在主线程关闭 Worker,还是在 Worker 线程内部关闭 Worker,Worker 线程当前的 Event Loop 中的任务会继续执行。至于 Worker 线程下一个 Event Loop 中的任务,则会被直接忽略,不会继续执行。
区别是,在主线程手动关闭 Worker,主线程与 Worker 线程之间的连接都会被立刻停止,此时如果继续调用postMessage() 方法发送消息,但主线程不会再接收到消息。而在 Worker 线程内部关闭 Worker,不会直接断开与主线程的连接,而是等 Worker 线程当前的 Event Loop 所有任务执行完,再关闭。也就是说,此时Worker线程可以继续调用 postMessage()方法。
3.6 Worker 线程引入JavaScript文件
有时候,我们需要在Worker 线程中处理一些复杂的业务逻辑,为了保证代码整体结构的美观,我们希望把代码单独开来,而不是都塞到Worker.js 里。为此,我们可以使用Web Work提供的importScripts() 方式来引入另一个JavaScript文件,而通过此方法加载JavaScript文件是不受同源策略约束的。
add.js
const add = (a, b) => a + b;
然后,在Worker线程中使用importScripts引入add.js。
importScripts('./add.js');
console.log(add(1, 2));
3.7 ESModule
有时候,当我们使用importScripts()方式导入JavaScript文件时出现执行失败,分析后发现原来项目的 JavaScript 文件都用的是 ESModule 模式。那么对于这种场景,我们需要怎么引入呢?
对于这种场景,我们需要在初始化Worker时传入可选参数option,我们可以直接使用 module 模式初始化 worker 线程。
const worker = new Worker('/worker.js',
type: 'module'
);
四、SharedWorker
SharedWorker 是一种特殊类型的 Worker,可以被多个浏览上下文访问,比如多个 windows,iframes 和 workers,但使用的前提是这些浏览上下文必须同源。
SharedWorker 线程的创建和使用跟 Worker 类似,事件和方法也基本一样。不同点在于,主线程与 SharedWorker 线程是通过MessagePort建立起链接,数据通讯方法都挂载在SharedWorker.port上。
并且需要说明的是,如果采用 addEventListener 来接收 message 事件,那么在主线程初始化SharedWorker() 后,还要调用 SharedWorker.port.start() 方法来手动开启端口。
SharedWorker的基本使用:
const myWorker = new SharedWorker('./sharedWorker.js');
myWorker.port.start(); // 开启端口
myWorker.port.addEventListener('message', msg =>
console.log(msg.data);
)
如果采用 onmessage()方法接收事件,则默认开启端口,不需要再手动调用SharedWorker.port.start()方法。
const myWorker = new SharedWorker('./sharedWorker.js');
myWorker.port.onmessage = msg =>
console.log(msg.data);
;
对于SharedWorker的调试,我们可以在 sharedWorker 线程里使用 console 打印信息,不会出现在主线程的的控制台中。如果你想调试 sharedWorker,需要在 Chrome 浏览器输入 chrome://inspect/ ,这里能看到所有正在运行的 sharedWorker,然后开启一个独立的 dev-tool 面板。
以上是关于一文了解Web Worker的主要内容,如果未能解决你的问题,请参考以下文章