Web Worker 为 Web 内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。此外,他们可以使用 XMLHttpRequest 执行 I/O (尽管 responseXML 和通道属性总是为空)。一旦创建, 一个 worker 可以将消息发送到创建它的 JavaScript 代码, 通过将消息发布到该代码指定的事件处理程序 (反之亦然)。
如我们所知,JavaScript 一直是属于单线程环境,我们无法同时运行两个 JavaScript 脚本;
但是试想一下,如果我们可以同时运行两个(或者多个)JavaScript 脚本,一个来处理 UI 界面(一直以来的用法),一个来处理一些复杂计算,那么程序的整个架构将会发生很多变化,我们的任务将更有区分性和条理性,同时可以更充分利用设备的硬件计算能力(多核运算),这将大大有利于提高我们的页面性能。
在 html5 的新规范中,实现了 Web Worker 来引入 JavaScript 的 "多线程" 技术,他的能力让我们可以在页面主运行的 JavaScript 线程中加载运行另外单独的一个或者多个 JavaScript 线程;
当然 Web Worker 提供的多线程编程能力并不像我们传统意义上的多线程编程,它不像其他的多线程语言 (Java、C++ 等),主程序线程和 Worker 线程之间,Worker 线程之间,不会共享任何作用域或资源,他们间唯一的通信方式就是一个基于事件监听机制的 message(下文将具体描述);
同时,这并不意味着 JavaScript 语言本身就支持了多线程,对于 JavaScript 语言本身它仍是运行在单线程上的, Web Worker 只是浏览器(宿主环境)提供的一个能力/API。
(以下例子统一约定:main.js 为页面运行的主要脚本文件,workder.js 为 Web Worker 脚本的文件)
实例化运行一个 Worker 很简单,我们只需要
一个
- new
全局对象即可:
- Worker
, 接受一个 filepathname String 参数,用于指定 Worker 脚本文件的路径;
- new Worker(filepathname)
- // main.js
- var worker = new Worker('./worker.js');
- // worker.js
- console.log('WORKER TASK: ', 'running');
用浏览器打开,我们可以看到 console 答应出来:
- WORKER TASK: running worker.js:1
说明我们加载并且执行到了这个 Worker 脚本。
当实例运行了一个 Worker 线程之后,两个线程是运行在完全独立的环境中,他们之间的通信是通过基于事件监听机制的 message 来实现的,
之后会返回一个实例对象,它包含一个
- new Worker()
方法,可以通过调用这个方法来给 Worker 线程传递信息;同时我们可以给这个对象监听事件,这样,就能在 Worker 中触发事件通信的时候接收到数据了;具体实现:
- postMessage
- // main.js
- var worker = new Worker('./worker.js');
- // 监听事件
- worker.addEventListener('message', function (e) {
- console.log('MAIN: ', 'RECEIVE', e.data);
- });
- // 或者可以使用 onMessage 来监听事件:
- // worker.onmessage = function () {
- // console.log('MAIN: ', 'RECEIVE', e.data);
- //};
- // 触发事件,传递信息给 Worker
- worker.postMessage('Hello Worker, I am main.js');
在 Worker 的脚本中,我们可以调用全局函数
和给全局的
- postMessage
赋值来发送和监听数据和事件:
- onmessage
- // worker.js
- console.log('WORKER TASK: ', 'running');
- // 监听事件
- onmessage = function (e) {
- console.log('WORKER TASK: ', 'RECEIVE', e.data);
- // 发送数据事件
- postMessage('Hello, I am Worker');
- }
- // 或者使用 addEventListener 来监听事件
- //addEventListener('message', function (e) {
- // console.log('WORKER TASK: ', 'RECEIVE', e.data);
- // ...
- //});
可以在 console 中看到,我们完成了一次两个线程之间的数据通信。
当然,这里传递的是一个 String 类型的数据,实际上,它支持 JavaScript 中所有类型的数据传递,可以传递一个 Object 数据;然而,值得注意的是,这里的数据传递(主要是 Object 类型)并不是共享,而是复制,发送端的数据和接收端的数据是复制而来,并不指向同一个对象,并且,这里的复制不是简单的便利拷贝,而是通过两端的序列化 / 解序列化来实现的,一般来说浏览器会通过 JSON 编码 / 解码;当然,这里的更多细节部分会由浏览器来处理,我们并不需要关系,只需要明白两端的数据是复制而来,互相独立的。
如果在某个时机不想要 Worker 继续运行了,那么我们需要终止掉这个线程,可以调用
的
- worker
方法 :
- terminate
- var worker = new Worker('./worker.js');
- ...
- worker.terminate();
当我们需要监听 worker 出现运行时错误的时候,可以在
对象监听
- worker
事件:
- error
- // main.js
- var worker = new Worker('./worker.js');
- // 监听消息事件
- worker.addEventListener('message',
- function(e) {
- console.log('MAIN: ', 'RECEIVE', e.data);
- });
- // 或者可以使用 onMessage 来监听事件:
- // worker.onmessage = function () {
- // console.log('MAIN: ', 'RECEIVE', e.data);
- //};
- // 监听 error 事件
- worker.addEventListener('error',
- function(e) {
- console.log('MAIN: ', 'ERROR', e);
- console.log('MAIN: ', 'ERROR', 'filename:' + e.filename + '---message:' + e.message + '---lineno:' + e.lineno);
- });
- // 或者可以使用 onMessage 来监听事件:
- // worker.onerror = function () {
- // console.log('MAIN: ', 'ERROR', e);
- //};
- // 触发事件,传递信息给 Worker
- worker.postMessage({
- m: 'Hello Worker, I am main.js'
- });
- // worker.js
- console.log('WORKER TASK: ', 'running');
- // 监听事件
- onmessage = function (e) {
- console.log('WORKER TASK: ', 'RECEIVE', e.data);
- // 发送数据事件
- // 注意:这里的 hhh 变量在 worker.js 中并未定义,所以这里执行过程中会错处
- postMessage( hhh );
- }
运行程序可以才 console 看到,main.js 中接收到来自 worker.js 中的一个运行错误,在监听事件的函数中接受一个参数
这个事件对象中有几个比较重要的参数需要我们注意:
- event
如前文所述,在 Worker 线程的运行环境中没有 window 全局对象,也无法访问 DOM 对象,所以一般来说他只能来执行纯 JavaScript 的计算操作。
但是,他还是可以获取到部分浏览器提供的 API 的:
:有了设计个函数,就可以在 Worker 线程中执行定时操作了;
- setTimeout(), clearTimeout(), setInterval(), clearInterval()
对象:意味着我们可以在 Worker 线程中执行 ajax 请求;
- XMLHttpRequest
对象:可以获取到 ppName,appVersion,platform,userAgent 等信息;
- navigator
对象(只读):可以获取到有关当前 URL 的信息;
- location
可以通过 Worker 环境中的全局函数
加载外部 js 脚本到当前 Worker 脚本中,它接收多个参数,参数都为加载脚本的链接字符串,比如:
- importScripts()
- // main.js
- var worker = new Worker('./worker1.js');
- // worker1.js
- console.log('hello, I,m worker 1');
- importScripts('worker2.js', 'worker3.js');
- // 或者
- // importScripts('worker2.js');
- // importScripts('worker3.js');
- // worker2.js
- console.log('hello, I,m worker 2');
- // worker3.js
- console.log('hello, I,m worker 3');
在这里,我们在 main.js 中运行了 worker1.js 线程,然后在 worker1.js 中加载了 worker2.js 和 worker3.js,在 console 中,可以看到他们全部执行了。
我们可以在一个 Worker 脚本中去实例化另一个 Worker,这成为子 Worker,但是这个特性目前大部分浏览器还未实现,所以不展开阐述。
对于 Web Worker ,一个 tab 页面只能对应一个 Worker 线程,是相互独立的;
而 SharedWorker 提供了能力能够让不同标签中页面共享的同一个 Worker 脚本线程;
当然,有个很重要的限制就是它们需要满足同源策略,也就是需要在同域下;
在页面(可以多个)中实例化 Worker 线程:
- // main.js
- var myWorker = new SharedWorker("worker.js");
- myWorker.port.start();
- myWorker.port.postMessage("hello, I'm main");
- myWorker.port.onmessage = function(e) {
- console.log('Message received from worker');
- }
- // worker.js
- onconnect = function(e) {
- var port = e.ports[0];
- port.addEventListener('message', function(e) {
- var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
- port.postMessage(workerResult);
- });
- port.start();
- }
在 SharedWorker 的使用中,我们发现对于 SharedWorker 实例对象,我们需要通过 port 属性来访问到主要方法;
同时在 Worker 脚本中,多了个全局的
函数,同时在函数中也需要去获取一个 post 对象来进行启动以及操作;
- connect()
这是因为,多页页面共享一个 SharedWorker 线程时,在线程中需要去判断和区分来自不同页面的信息,这是最主要的区别和原因。
遗憾的是,对于 SharedWorker,兼容性现在而言也是大部分浏览器还未实现。
Web Worker
Web Worker 的实现为前端程序带来了后台计算的能力,可以实现主 UI 线程与复杂计运算线程的分离,从而极大减轻了因计算量大而造成 UI 阻塞而出现的界面渲染卡、掉帧的情况,并且更大程度地利用了终端硬件的性能;
同时把程序之间的任务更清晰、条理化;
其主要应用有几个场景:
来源: