简介 什么是 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 的兼容性:
可以看到,基本上所有的浏览器都支持 worker,不过有些浏览器只支持部分的方法。
如果想要立马结束一个 worker,我们可以使用 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 的浏览器兼容性:
可以看到,比 worker 的兼容性要低很多,只有部分浏览器才支持这个高级特性。
worker 和 main thread 之间的数据传输 我们知道 worker 和 main thread 之间是通过 postMessage 和 onMessage 进行交互的。这里面涉及到了数据传输的问题。
实际上数据在 worker 和 main thread 之间是以拷贝的方式并且是以序列化的形式进行传输的。