咱们都知道,JavaScript 是单线程的,在同一时刻只能处理一个任务,咱们会经过 setTimeout()、setInterval()、ajax 和事件处理程序等技术模拟“并行”。但都不是真正意义上的并行:html
Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它容许一段 JavaScript 程序运行在主线程以外的另一个线程中。前端
这在很大程度上利用了如今不断升级的电脑计算能力:可以在同一时间平行处理两个任务。webpack
游泳、健身了解一下: 博客、 前端积累文档、 公众号、 GitHub
当咱们有些任务须要花费大量的时间,进行复杂的运算,就会致使页面卡死:用户点击页面须要很长的时间才能响应,由于前面的任务还未完成,后面的任务只能排队等待。对用户来讲,这样的体验无疑是糟糕的,web worker 就是为了解决这种花费大量时间的复杂运算而诞生的!git
WebWorker 容许在主线程以外再建立一个 worker 线程,在主线程执行任务的同时,worker 线程也能够在后台执行它本身的任务,互不干扰。github
这样就让 JS 变成多线程的环境了,咱们能够把高延迟、花费大量时间的运算,分给 worker 线程,最后再把结果返回给主线程就能够了,由于时间花费多的任务被 web worker 承担了,主线程就会很流畅了!web
codepen,这里我写了一个 class,里面有详细注释,能够参考一下。ajax
主线程调用new Worker()
构造函数,新建一个 worker 线程,构造函数的参数是一个 url,生成这个 url 的方法有两种:数组
脚本文件:浏览器
const worker = new Worker('https://~.js');
由于 worker 的两个限制:缓存
file://
),它所加载的脚本必须来自网络。能够看到限制仍是比较多的,若是要使用这种形式的话,在项目中推荐把文件放在静态文件夹中,打包的时候直接拷贝进去,这样咱们就能够拿到固定的连接了,
字符串形式:
const data = ` // worker线程 do something `; // 转成二进制对象 const blob = new Blob([data]); // 生成url const url = window.URL.createObjectURL(blob); // 加载url const worker = new Worker(url);
栗子中就是使用这种形式的,方便咱们演示。
在项目中:咱们能够把worker线程的逻辑写在js文件里面,而后字符串化,而后再export、import,配合webpack进行模块化管理,这样就很容易使用了。
worker.postMessage({ hello: ['hello', 'world'] });
它们相互之间的通讯能够传递对象和数组,这样咱们就能够根据相互之间传递的信息来进行一些操做,好比能够设置一个type
属性,当值为hello
时执行什么函数,当值为world
的时候执行什么函数。
值得注意的是:它们之间通讯是经过拷贝的形式来传递数据的,进行传递的对象须要通过序列化,接下来在另外一端还须要反序列化。这就意味着:
worker.onmessage = function (e) { console.log('父进程接收的数据:', e.data); // doSomething(); }
Worker 线程一旦新建成功,就会始终运行,这样有利于随时响应主线程的通讯。
这也是 Worker 比较耗费计算机的计算资源(CPU
)的缘由,一旦使用完毕,就应该关闭 worker 线程。
worker.terminate(); // 主线程关闭worker线程
// worker线程报错 worker.onerror = e => { // e.filename - 发生错误的脚本文件名;e.lineno - 出现错误的行号;以及 e.message - 可读性良好的错误消息 console.log('onerror', e); };
也能够像我给出的栗子同样,把两个报错放在一块儿写,有报错把信息传出来就行了。
worker 线程的执行上下文是一个叫作WorkerGlobalScope
的东西跟主线程的上下文(window)不同。
咱们可使用self
/WorkerGlobalScope
来访问全局对象。
self.onmessage = e => { console.log('主线程传来的信息:', e.data); // do something };
self.postMessage({ hello: [ '这条信息', '来自worker线程' ] });
self.close()
Worker 线程可以访问一个全局函数 imprtScripts()来引入脚本,该函数接受 0 个或者多个 URI 做为参数。
importScripts('http~.js','http~2.js');
由于 worker 创造了另一个线程,不在主线程上,相应的会有一些限制,咱们没法使用下列对象:
咱们可使用下列对象/功能:
栗子最下方有。
在 worker 线程内再新建 worker 线程就不能使用window.URL.createObjectURL(blob)
,须要使用同源的脚本文件来建立新的 worker 线程,由于咱们没法访问到window
对象。
这里不方便演示,跟在主线程建立 worker 线程是一个套路,只是改为了脚本文件形式建立 worker 线程。
由于主线程与 worker 线程之间的通讯是拷贝关系,当咱们要传递一个巨大的二进制文件给 worker 线程处理时(worker 线程就是用来干这个的),这时候使用拷贝的方式来传递数据,无疑会形成性能问题。
幸运的是,Web Worker 提供了一中转移数据的方式,容许主线程把二进制数据直接转移给子线程。这种方式比原先拷贝的方式,有巨大的性能提高。
一旦数据转移到其余线程,原先线程就没法再使用这些二进制数据了,这是为了防止出现多个线程同时修改数据的麻烦局面
下方栗子出自浅谈 HTML5 Web 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 // 字符串形式建立worker线程 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)); // 使用这个格式(a,[a]) 来转移二进制数据 myWorker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]); // 发送数据、转移数据 console.log(uInt8Array.length); // 传递后长度:0,原先线程内没有这个数据了
二进制数据有:File、Blob、ArrayBuffer 等类型,也容许在 worker 线程之间发送, 这对于影像处理、声音处理、3D 运算等就很是方便了,不会产生性能负担
好比用户输入时,咱们在后台检索答案,或者帮助用户联想,纠错等操做.
没有找到具体的制定日期,有篇博客是在 10 年的 7 月份写的,也就是说 web worker 至少出现了八年了,如下兼容摘自MDN:
Chrome:4, Firefox:3.5, IE:10.0, Opera:10.6, Safari:4
如今兼容仍是作的比较好的,若是实在不放心的话:
if (window.Worker) { ... }else{ ... }
Web Worker的出现,给浏览器带来了后台计算的能力,把耗时的任务分配给worker线程来作,在很大程度上缓解了主线程UI渲染阻塞的问题,提高页面性能。
使用起来也不复杂,之后有复杂的问题,记得要丢给咱们浏览器的后台(web worker)来处理
看完以后,必定要研究一下文中的栗子,本身鼓捣鼓捣,实践出真知!
PS: 推荐一下我上个月写的手摸手教你使用WebSocket,感兴趣的能够看一下。
以上2018.11.25
参考资料: