一.Web Workers是什么
Web Workers是一种支持任务在后台线程运行的机制,没错,Web Worker提供了行为受限的多线程
虽然浏览器中js的执行环境仍然是单线程的,但多线程在浏览器中早就存在了,比如异步XHR就是一个行为受限的线程,它独立于主线程,通过内置的消息机制来通信,不存在一般多线程的并发问题
Web Workers提供一个独立于主线程的context,在这个context里可以使用受限的API(禁止操作DOM,可能无法访问Cache等等)。多线程最大的好处就是不会阻塞UI,可以用多线程来完成时耗巨大的任务(比如音频/视频解码,以及在此基础上的数据处理),甚至如果API允许的话,利用Web Workers就可以自行实现XHR
P.S.是否允许Web Worker访问Cache,目前(2015/11/29)还有争议,Chrome43和FF支持,IE和Safari支持性未知
一点废话:Google的Gears插件提出了Worker Pool API,它就是Web Workers的“原型”,最初希望能够增强浏览器的功能,比如支持离线浏览(离线访问缓存页面,重新上线后提交离线操作),但目前(2015/11/29)已经被弃用了
Google Gears API
The Google Gears API is no longer available. Thank you for your interest.
二.Web Workers有什么用
后台线程,最大的作用就是do stuff。支持后台线程意味着浏览器能够承担更多更重的任务了,可以把一部分原本由服务端承担的运算(比如排序,编码/解码,模版生成)交给客户端,以减轻服务端压力,而不会影响用户体验(等待服务端返回结果和等待本地另一个线程返回结果一样,都是等待,而且后者可能更快)
笔者认为Web Workers有以下适用场景:
音频/视频解码
如果尝试过
audioContext.decodeAudioData
之类的操作就会发现,我们迫切需要一个能“干重活”的后台线程图片预处理
比如头像上传前的裁剪,甚至添加水印、拼合、添马赛克,如果在客户端能够完成,就能避免大量的临时文件传输
排序等数据处理算法
减轻服务器压力
客户端模版
比如markdown,或者服务端返回JSON,客户端拿到后交给后台线程解析并应用模版HTML生成页面,这些操作都由客户端完成的话,需要传输的东西就更少了
三.Web Workers怎么用
会说话的示例代码如下:
//---主页面
if (window.Worker) {
var worker = new Worker('worker.js');
var data = {a: 1, b: [1, 2, 3], c: 'string'};
worker.postMessage(data);
worker.onmessage = function(e) {
console.log('main thread received data');
console.log(e.data);
// 接到消息立即停止worker,onerror将不会触发
// worker.terminate();
// terminate之后收不到后续消息,但post不报错
// worker.postMessage(1);
}
worker.onerror = function(err) {
console.log('main thread received err');
console.log(err.message);
// 阻止报错
err.preventDefault();
}
}
//---end 主页面
//---worker.js
// 可以引入其它依赖
// importScripts('lib.js');
// importScripts('a.js', 'b.js');
onmessage = function(e) {console.log(self); // 看看global变量身上有些什么
var data = e.data;
console.log('worker received data');
console.log(data);
var res = data;
res.resolved = true;
postMessage(res);
setTimeout(function() {
throw new Error('error occurs');
// close,立即停止,相当于主线程中的worker.terminate()
// close();
}, 100);
};
//---end worker.js
注意:
worker
的global context并不是window
,而是self
,self
也提供一系列接口,包括self.JSON
、self.Math
、self.console
等等,最直观的区别是document
对象没了,但location
、navigator
还在Worker的原型是DedicatedWorkerGlobalScope,与SharedWGS不同,Worker是专用的,只有创建Worker的脚本才能访问自己创建的worker
worker
里不允许操作DOM,但支持WebSocket、IndexedDB、XHR等等,各浏览器支持性有差异,具体请查看Functions and classes available to Web Workers – Web APIs | MDN注意:XHR虽然可以用,但XHR对象的
responseXML
和channel
属性永远返回null
主线程和worker线程收发消息方式一致(
postMessage
发,onmessage/onerror
收,数据从MessageEvent的data
属性取),线程之间传递的是值copy,而不是共享引用IE10和FF支持在
worker
里new Worker,只要这些worker和父页面是同源的支持性问题,建议做特性检测
if (window.Worker) { // new Worker... }
importScripts
可以引入其它js文件,外部文件中的全局变量将被粘在self
上,worker
里可以直接引用。importScripts
是同步的,下载并执行完毕后执行下一行
四.Shared Worker
上面我们关注的Worker属于dedicated worker(专用worker),只有创建worker对象的context有worker的访问权。而Shared Worker支持跨context的worker共享,比如window与iframe,iframe与iframe
由于它们具有共享的属性,可以保持一个应用程序在不同窗口内的相同状态,并且不同窗口的页面通过同一共享worker脚本保持和报告状态
用localStorage也可以实现状态同步,zxx前辈的例子:Page Visibility(网页可见性) API与登录同步引导页实例页面。有兴趣的话可以查看原文:Page Visibility(页面可见性) API介绍、微拓展
笔者能想到Shared Worker的唯一的适用场景可能就是实现应用状态同步机制了,虽然cookie、localStorage都能够存储状态,但Shared Worker能够实现一套复杂的状态控制机制。简言之,cookie和localStorage里只能放数据,Shared Worker里除了能放数据,还能执行代码
此外,Shared Worker好像没什么用了,毕竟能new Worker
已经很不错了,锦上添花的东西作用不会让人诧异
示例代码如下:
//---主页面
if (window.SharedWorker) {
var sworker = new SharedWorker('sharedworker.js');
// 1.onmessage隐式调用start
// sworker.port.onmessage = function(e) {
// console.log('main thread received data:');
// console.log(e.data);
// }
// 2.或者addEventListener再显式调用start
sworker.port.addEventListener('message', function(e) {
console.log('main thread received data:');
console.log(e.data);
});
sworker.port.start();
sworker.port.postMessage([1, 2, 3]);
}
//---end 主页面
//---sharedworker.js
var count = 0;
onconnect = function(e) {
// 记录连接数
count++;
var port = e.ports[0];
// 1.onmessage隐式调用start
port.onmessage = function(e) {
//!!! console.log失效了
console.log('will not occur in console');
port.postMessage({receivedData: e.data, count: count});
//!!! 错误不会抛回给sharedworker创建者,静默失败
//!!! 如果sharedworker本身有语法错误,也会静默失败,而且在这之前的postMessage也将无效
// a*; // 制造一个语法错误
setTimeout(function() {
throw new Error('error occurs');
}, 100);
};
// 2.或者addEventListener再显式调用start
// port.addEventListener('message', function(e) {
// //!!! console.log失效了
// console.log('will not occur in console');
// port.postMessage('sharedworker received data: ' + e.data);
// });
// port.start();
};
//---end sharedworker.js
在线DEMO:testSharedWorker DEMO
测试方法:打开上面的链接,看console发现count
为1,不管怎么刷新刷新都是1,再开一个标签页打开该页面,发现count
变成2了,然后刷新刷新刷新,发现count
变成5了,回到之前的标签页,刷新,count
变成6了…
五.线程安全问题
线程安全方面,不存在并发可能引发的各种问题,因为Worker线程的行为受到了限制(worker
无法访问非线程安全的组件和DOM)
六.其它Worker
除了上面讨论的Web Workers,还有这些Worker:
ServiceWorkers(类似于代理服务器,提供离线服务)
ChromeWorker(开发浏览器插件中可用的Worker)
Audio Workers(支持音频处理,暂无任何浏览器支持,2015/11/29)