本周精读的文章是 speedy-introduction-to-web-workers,是一篇 Web Workers 快速入门的文章,借精读这篇文章的机会,谈谈对 Web Workers 的理解与运用。javascript
就像分工,你只负责编码,而你的朋友负责设计,那你就能够专心把本身的事情作好,并且更快速的完成任务。
本文经过一个比方,描述了 Web Workers 的两大特征:前端
由于浏览器是单线程的,任何大量耗时的 JS 任务都会卡住界面,使浏览器没法响应任何操做,这样的用户体验很是糟糕。Web Workers 能够将耗时任务拆解出去,下降主线程的压力,避免主线程无响应。java
但 CPU 资源是有限的,Web Workers 并不能增长整体运行效率,算上通讯的损耗,总体计算效率会有必定的降低。
const worker = new Worker("../src/worker.js");
上述代码中,worker
就是一个 Web Workers 实例,执行的代码是 ../src/worker.js
路径下的文件。webpack
Web Workers 用来执行异步脚本,只要掌握了它与主线程通讯的方式,就能够在指定时机运行异步脚本,并在运行完时将结果传递给主线程。git
const worker = new Worker("../src/worker.js"); worker.onmessage = e => {}; worker.postMessage("Marco!");
每一个 worker
实例经过 onmessage
接收消息,经过 postMessage
发送消息。github
self.onmessage = e => {}; self.postMessage("Marco!");
和主线程代码相似,在 Web Workers 代码中,也是 onmessage
接收消息,这个消息来自主线程或者其它 Workers。也能够经过 postMessage
发送消息。web
worker.terminate();
文章内容就这么多,是否是有写太简单了呢!笔者结合本身的使用经验,再补充一些知识。后端
对象转移就是将对象引用零成本转交给 Web Workers 的上下文,而不须要进行结构拷贝。浏览器
这里要解释的是,主线程与 Web Workers 之间的通讯,并非对象引用的传递,而是序列化/反序列化的过程,当对象很是庞大时,序列化和反序列化都会消耗大量计算资源,下降运行速度。架构
上面的图充分证实了,大对象传递,使用对象转移各项指标都优于结构拷贝。
对象转移使用方式很简单,给 postMessage
增长一个参数,把对象引用传过去便可:
var ab = new ArrayBuffer(1); worker.postMessage(ab, [ab]);
浏览器兼容性也不错:Currently Chrome 17+, Firefox, Opera, Safari, IE10+。更具体内容,能够看 Transferable Objects: Lightning Fast!。
须要注意的是,对象引用转移后,原先上下文就没法访问此对象了,须要在 Web Workers 再次将对象还原到主线程上下文后,主线程才能正常访问被转交的对象。
Web Workers 优点这么大,但用起来须要在同域下建立一个 JS 文件实在不方便,尤为在先后端分离作的比较完全的团队,前端团队能控制的仅仅是一个 JS 文件。那么下面给出几个不用 JS 文件,就建立 Web Workers 的方法:
worker-loader 是一个 webpack 插件,能够将一个普通 JS 文件的所有依赖提取后打包并替换调用处,以 Blob 形式内联在源码中。
import Worker from "worker-loader!./file.worker.js"; const worker = new Worker();
上述代码的魔术在于,转化成下面的方式执行:
const blob = new Blob([codeFromFileWorker], { type: "application/javascript" }); const worker = new Worker(URL.createObjectURL(blob));
第二种方式由第一种方式天然带出:若是不想用 webpack 插件,那本身经过 Blob 的方式建立也能够:
const code = ` importScripts('https://xxx.com/xxx.js'); self.onmessage = e => {}; `; const blob = new Blob([code], { type: "application/javascript" }); const worker = new Worker(URL.createObjectURL(blob));
看上去代码更轻量一些,不过问题是当遇到复杂依赖时,若是不能把依赖都转化为脚本经过 importScripts
方式引用,就没法访问到主线程环境中的包。若是真的遇到了这个问题,能够用第一种 webpack 插件的方式解决,这个插件会自动把文件全部依赖都打包进源码。
为何 postMessage 会造成队列,为何要管理它?
首先在 Web Workers 架构设计上就必须作成队列,由于调用 postMessage
时,对应的 Web Workers 不必定完成了初始化,因此浏览器底层必须管理一个队列,在 Web Workers 初始化完毕时,依次消费,这样才能确保任什么时候候发出的 postMessage
都能被 Web Workers 接收到。
其次,为何要手动维护这个队列,缘由可能取决于以下几点:
postMessage
还没来得及消费,就不要发送新的消息,或者丢弃新的消息,这时候须要经过双向通讯拿到 Web Workers 的执行结果回执,手动控制队列。如上图所示,对于每次用户输入都要进行的 SQL Parser 很耗时,及时放在 Web Workers 也可能致使将 Workers 撑爆到无响应,这是不只要使用多 Workers 缓冲池,还要对待执行队列进行过滤,由于用户永远只关心最后一次输入的 Parser 结果。
因为 Web Workers 运算被卡住时,除了销毁 Worker 没有别的办法,而销毁 Worker 的成本比较高,不能对每个用户输入都销毁并新建 Web Workers,因此利用 Workers 缓冲池,当缓冲池满了,新的消费队列又进来的时候,能够销毁所有 Workers 缓冲池,换一批新缓冲池从新消费用户输入。
Web Workers 是拆解异步计算的好帮手,vscode 网页版也经过 Web Workers 异步完成代码提示和高亮,笔者有对比过,发现 Web Workers 性能提高很是明显。
管理好你的 Web Workers 消息队列,谨防同步计算让 Web Workers 失去响应!创建一个智能的消息队列,根据业务需求设计一个最好的队列消费模型吧!
讨论地址是: 精读《谈谈 Web Workers》 · Issue #108 · dt-fe/weekly
若是你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。