众所周知,Javascript是运行在单线程环境中,也就是说没法同时运行多个脚本。假设用户点击一个按钮,触发了一段用于计算的Javascript代码,那么在这段代码执行完毕以前,页面是没法响应用户操做的。可是,若是将这段代码交给Web Worker去运行的话,那么状况就不同了:浏览器会在后台启动一个独立的worker线程来专门负责这段代码的运行,所以,页面在这段Javascript代码运行期间依然能够响应用户的其余操做。javascript
Web Worker 是HTML5标准的一部分,这一规范定义了一套 API,它容许一段JavaScript程序运行在主线程以外的另一个线程中。
值得注意的是, Web Worker 规范中定义了两类工做线程,分别是专用线程Dedicated Worker和共享线程 Shared Worker,其中,Dedicated Worker只能为一个页面所使用,而Shared Worker则能够被多个页面所共享。java
只需调用Worker() 构造函数并传入一个要在 worker 线程内运行的脚本的URI,便可建立一个新的worker。web
var myWorker = new Worker("my_task.js");
// my_task.js中的代码
var i = 0;
function timedCount(){
i = i+1;
postMessage(i);
setTimeout(timedCount, 1000);
}
timedCount();复制代码
另外,经过URL.createObjectURL()建立URL对象,能够实现建立内嵌的workerchrome
var myTask = ` var i = 0; function timedCount(){ i = i+1; postMessage(i); setTimeout(timedCount, 1000); } timedCount(); `;
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));复制代码
这样,就能够结合NEJ、Webpack进行模块化管理、打包了。canvas
注意:传入 Worker 构造函数的参数 URI 必须遵循同源策略。Worker线程的建立的是异步的,主线程代码不会阻塞在这里等待worker线程去加载、执行指定的脚本文件,而是会当即向下继续执行后面代码。浏览器
提示:本文全部的示例代码都可直接拷贝到chrome控制台中运行。框架
Worker 与其主页面之间的通讯是经过 onmessage 事件和 postMessage() 方法实现的。异步
在主页面与 Worker 之间传递的数据是经过拷贝,而不是共享来完成的。传递给 Worker 的对象须要通过序列化,接下来在另外一端还须要反序列化。页面与 Worker 不会共享同一个实例,最终的结果就是在每次通讯结束时生成了数据的一个副本。mvvm
也就是说,Worker 与其主页面之间只能单纯的传递数据,不能传递复杂的引用类型:如经过构造函数建立的对象等。而且,传递的数据也是通过拷贝生成的一个副本,在一端对数据进行修改不会影响另外一端。模块化
var myTask = ` onmessage = function (e) { var data = e.data; data.push('hello'); console.log('worker:', data); // worker: [1, 2, 3, "hello"] postMessage(data); }; `;
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.onmessage = function (e) {
var data = e.data;
console.log('page:', data); // page: [1, 2, 3, "hello"]
console.log('arr:', arr); // arr: [1, 2, 3]
};
var arr = [1,2,3];
myWorker.postMessage(arr);复制代码
前面介绍了简单数据的传递,其实还有一种性能更高的方法来传递数据,就是经过可转让对象将数据在主页面和Worker之间进行来回穿梭。可转让对象从一个上下文转移到另外一个上下文而不会通过任何拷贝操做。这意味着当传递大数据时会得到极大的性能提高。和按照引用传递不一样,一旦对象转让,那么它在原来上下文的那个版本将不复存在。该对象的全部权被转让到新的上下文内。例如,当你将一个 ArrayBuffer 对象从主应用转让到 Worker 中,原始的 ArrayBuffer 被清除而且没法使用。它包含的内容会(完整无差的)传递给 Worker 上下文。
var uInt8Array = new Uint8Array(1024*1024*32); // 32MB
for (var i = 0; i < uInt8Array .length; ++i) {
uInt8Array[i] = i;
}
console.log(uInt8Array.length); // 传递前长度:33554432
var myTask = ` onmessage = function (e) { var data = e.data; console.log('worker:', data); }; `;
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);
console.log(uInt8Array.length); // 传递后长度:0复制代码
Worker 线程可以访问一个全局函数imprtScripts()来引入脚本,该函数接受0个或者多个URI做为参数。
浏览器加载并运行每个列出的脚本,每一个脚本中的全局对象都可以被 worker 使用。若是脚本没法加载,将抛出 NETWORK_ERROR 异常,接下来的代码也没法执行。而以前执行的代码(包括使用 window.setTimeout() 异步执行的代码)依然可以运行。importScripts() 以后的函数声明依然会被保留,由于它们始终会在其余代码以前运行。
注意:脚本的下载顺序不固定,但执行时会按照传入 importScripts() 中的文件名顺序进行。这个过程是同步完成的;直到全部脚本都下载并运行完毕, importScripts() 才会返回。
Worker执行的上下文,与主页面执行时的上下文并不相同,最顶层的对象并非window,而是个一个叫作WorkerGlobalScope的东东,因此没法访问window、以及与window相关的DOM API,可是能够与setTimeout、setInterval等协做。
WorkerGlobalScope做用域下的经常使用属性、方法以下:
一、self
咱们可使用 WorkerGlobalScope 的 self 属性来或者这个对象自己的引用
二、location
location 属性返回当线程被建立出来的时候与之关联的 WorkerLocation 对象,它表示用于初始化这个工做线程的脚步资源的绝对 URL,即便页面被屡次重定向后,这个 URL 资源位置也不会改变。
三、close
关闭当前线程
四、importScripts
咱们能够经过importScripts()方法经过url在worker中加载库函数
五、XMLHttpRequest
有了它,才能发出Ajax请求
六、setTimeout/setInterval以及addEventListener/postMessage
在主页面上调用terminate()方法,能够当即杀死 worker 线程,不会留下任何机会让它完成本身的操做或清理工做。另外,Worker也能够调用本身的 close() 方法来关闭本身
// 主页面调用
myWorker.terminate();
// Worker 线程调用
self.close();复制代码
当 worker 出现运行时错误时,它的 onerror 事件处理函数会被调用。它会收到一个实现了 ErrorEvent 接口名为 error的事件。该事件不会冒泡,而且能够被取消;为了防止触发默认动做,worker 能够调用错误事件的 preventDefault() 方法。
错误事件有三个实用的属性:filename - 发生错误的脚本文件名;lineno - 出现错误的行号;以及 message - 可读性良好的错误消息。
var myTask = ` onmessage = function (e) { var data = e.data; console.log('worker:', data); }; // 使用未声明的变量 arr.push('error'); `;
var blob = new Blob([myTask]);
var myWorker = new Worker(window.URL.createObjectURL(blob));
myWorker.onerror = function onError(e) {
// ERROR: Line 8 in blob:http://www.cnblogs.com/490a7c32-7386-4d6e-a82b-1ca0b1bf2469: Uncaught ReferenceError: arr is not defined
console.log(['ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message].join(''));
}复制代码
最后总结下Web Worker为javascript带来了什么,以及典型的应用场景。
能够加载一个JS进行大量的复杂计算而不挂起主进程,并经过postMessage,onmessage进行通讯,解决了大量计算对UI渲染的阻塞问题。
一、数学运算
Web Worker最简单的应用就是用来作后台计算,对CPU密集型的场景再适合不过了。
二、图像处理
经过使用从<canvas>
中获取的数据,能够把图像分割成几个不一样的区域而且把它们推送给并行的不一样Workers来作计算,对图像进行像素级的处理,再把处理完成的图像数据返回给主页面。
三、大数据的处理
目前mvvm框架愈来愈普及,基于数据驱动的开发模式也越愈发流行,将来大数据的处理也可能转向到前台,这时,将大数据的处理交给在Web Worker也是上上之策了吧。