0%

web worker的介绍和使用

简介

什么是 web worker 呢?从名字上就可以看出,web worker 就是在 web 应用程序中使用的 worker。这个 worker 是独立于 web 主线程的,在后台运行的线程。

web worker 的优点就是可以将工作交给独立的其他线程去做,这样就不会阻塞主线程。

Web Workers 的基本概念和使用

web workers 是通过使用 Worker()来创建的。

Worker 可以指定后台执行的脚本,并在脚本执行完毕之后通常 creator。

worker 有一个构造函数如下:

1
Worker("path/to/worker/script")

我们传入要执行脚本的路径,即可创建 worker。

Workers 中也可以创建新的 Workers,前提是这些 worker 都是同一个 origin。

我们看一下 worker 的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Worker extends EventTarget, AbstractWorker {
onmessage: ((this: Worker, ev: MessageEvent) => any) | null;
onmessageerror: ((this: Worker, ev: MessageEvent) => any) | null;

postMessage(message: any, transfer: Transferable[]): void;
postMessage(message: any, options?: PostMessageOptions): void;

terminate(): void;
addEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof WorkerEventMap>(type: K, listener: (this: Worker, ev: WorkerEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var Worker: {
prototype: Worker;
new(stringUrl: string | URL, options?: WorkerOptions): Worker;
};

可以看到 Worker 的构造函数可以传入两个参数,第一个参数可以是 string 也可以是 URL,表示要执行的脚本路径。

第二个参数是 WorkerOptions 选项,表示 worker 的类型,名字和权限相关的选项。

1
2
3
4
5
interface WorkerOptions {
credentials?: RequestCredentials;
name?: string;
type?: WorkerType;
}

除此之外,worker 可以监听 onmessage 和 onmessageerror 两个事件。

提供了两个方法:postMessage 和 terminate。

worker 和主线程都可以通过 postMessage 来给对方发送消息,也可以用 onmessage 来接收对方发送的消息。

还可以添加和移除 EventListener。

我们看一个使用 worker 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const first = document.querySelector('#number1');
const second = document.querySelector('#number2');

const result = document.querySelector('.result');

if (window.Worker) {
const myWorker = new Worker("worker.js");

first.onchange = function() {
myWorker.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}

second.onchange = function() {
myWorker.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}

myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
} else {
console.log('Your browser doesn\'t support web workers.')
}

上面的例子创建了一个 woker,并向 worker post 了一个消息。

再看一下 worker.js 的内容是怎么样的:

1
2
3
4
5
6
7
8
9
10
11
onmessage = function(e) {
console.log('Worker: Message received from main script');
const result = e.data[0] * e.data[1];
if (isNaN(result)) {
postMessage('Please write two numbers');
} else {
const workerResult = 'Result: ' + result;
console.log('Worker: Posting message back to main script');
postMessage(workerResult);
}
}

我们在主线程中向 worker postmessage,在 worker 中通过 onmessage 监听消息,然后又在 worker 中 post message,可以在 main 线程中通过 onmessage 来监听 woker 发送的消息。

这样就做到了一次完美的交互。

再看一下 worker 的兼容性:

image

可以看到,基本上所有的浏览器都支持 worker,不过有些浏览器只支持部分的方法。

如果想要立马结束一个 worker,我们可以使用 terminate:

1
myWorker.terminate();

要想处理 worker 的异常,可以使用 onerror 来处理异常。

如果 worker 的 script 比较复杂,需要用到其他的 script 文件,我们可以使用 importScripts 来导入其他的脚本:

1
2
3
4
importScripts();                         /* imports nothing */
importScripts('foo.js'); /* imports just "foo.js" */
importScripts('foo.js', 'bar.js'); /* imports two scripts */
importScripts('//example.com/hello.js'); /* You can import scripts from other origins */

Web Workers 的分类

Web Workers 根据工作环境的不同,可以分为 DedicatedWorker 和 SharedWorker 两种。

DedicatedWorker 的 Worker 只能从创建该 Woker 的脚本中访问,而 SharedWorker 则可以被多个脚本所访问。

上面的例子中我们创建的 worker 就是 DedicatedWorker。

怎么创建 sharedWorker 呢?

1
var myWorker = new SharedWorker('worker.js');

SharedWorker 有一个单独的 SharedWorker 类,和 dedicated worker 不同的是 SharedWorker 是通过 port 对象来进行交互的。

我们看一个 shared worker 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var first = document.querySelector('#number1');
var second = document.querySelector('#number2');

var result1 = document.querySelector('.result1');

if (!!window.SharedWorker) {
var myWorker = new SharedWorker("worker.js");

first.onchange = function() {
myWorker.port.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}

second.onchange = function() {
myWorker.port.postMessage([first.value, second.value]);
console.log('Message posted to worker');
}

myWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
console.log(e.lastEventId);
}
}

所有的 postMessage 和 onmessage 都是基于 myWorker.port 来的。

再看一下 worker 的代码:

1
2
3
4
5
6
7
8
9
onconnect = function(e) {
var port = e.ports[0];

port.onmessage = function(e) {
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
port.postMessage(workerResult);
}

}

worker 也是通过 port 来进行通信。

这里我们使用了 onconnect 用来监听父线程的 onmessage 事件或者 start 事件,这两种事件都可以启动一个 SharedWorker。

再看一下 sharedWorker 的浏览器兼容性:

image

可以看到,比 worker 的兼容性要低很多,只有部分浏览器才支持这个高级特性。

worker 和 main thread 之间的数据传输

我们知道 worker 和 main thread 之间是通过 postMessage 和 onMessage 进行交互的。这里面涉及到了数据传输的问题。

实际上数据在 worker 和 main thread 之间是以拷贝的方式并且是以序列化的形式进行传输的。