本文创做于 2018-12-12,2019-12-20 迁移至此
Web Worker (工做线程) 是 HTML5 中提出的概念,分为两种类型,专用线程(Dedicated Web Worker) 和共享线程(Shared Web Worker)。专用线程仅能被建立它的脚本所使用(一个专用线程对应一个主线程),而共享线程可以在不一样的脚本中使用(一个共享线程对应多个主线程)。javascript
专用线程能够看作是默认状况的 Web Worker,其加上修饰词的目的是为了与共享线程进行区分。本文会较为严格地区分二者,可能较为累赘,但我的认为这是必要的。若是单纯以 Web Worker
字样出现的地方指的是二者都会有的状况。html
Web Worker 的意义在于能够将一些耗时的数据处理操做从主线程中剥离,使主线程更加专一于页面渲染和交互。html5
根据 CanI Use 网站的统计,目前约有 93.05% 的浏览器支持专用线程。java
而对于共享线程,则仅有大约 41.66% 的浏览器支持。web
因为专用线程和共享线程的构造方法都包含在 window 对象中,咱们在使用二者以前能够对浏览器的支持性进行判断。算法
if (window.Worker) { // ... }
if (window.SharedWorker) { // ... }
专用线程由 Worker()
方法建立,能够接收两个参数,第一个参数是必填的脚本的位置,第二个参数是可选的配置对象,能够指定 type
、credentials
、name
三个属性。canvas
var worker = new Worker('worker.js') // var worker = new Worker('worker.js', { name: 'dedicatedWorker'})
共享线程使用 Shared Worker()
方法建立,一样支持两个参数,用法与 Worker()
一致。数组
var sharedWorker = new SharedWorker('shared-worker.js')
值得注意的是,由于 Web Worker 有同源限制,因此在本地调试的时候也须要经过启动本地服务器的方式访问,使用 file://
协议直接打开的话将会抛出异常。浏览器
Worker 线程和主线程都经过 postMessage()
方法发送消息,经过 onmessage
事件接收消息。在这个过程当中数据并非被共享的,而是被复制的。值得注意的是 Error
和 Function
对象不能被结构化克隆算法复制,若是尝试这么作的话会致使抛出 DATA_CLONE_ERR
的异常。另外,postMessage()
一次只能发送一个对象, 若是须要发送多个参数能够将参数包装为数组或对象再进行传递。服务器
关于 postMessage()
和结构化克隆算法(The structured clone algorithm)将在本文最后进行阐述。
下面是专用线程数据传递的示例。
// 主线程 var worker = new Worker('worker.js') worker.postMessage([10, 24]) worker.onmessage = function(e) { console.log(e.data) } // Worker 线程 onmessage = function (e) { if (e.data.length > 1) { postMessage(e.data[1] - e.data[0]) } }
在 Worker 线程中,self
和 this
都表明子线程的全局对象。对于监听 message
事件,如下的四种写法是等同的。
// 写法 1 self.addEventListener('message', function (e) { // ... }) // 写法 2 this.addEventListener('message', function (e) { // ... }) // 写法 3 addEventListener('message', function (e) { // ... }) // 写法 4 onmessage = function (e) { // ... }
主线程经过 MessagePort
访问专用线程和共享线程。专用线程的 port 会在线程建立时自动设置,而且不会暴露出来。与专用线程不一样的是,共享线程在传递消息以前,端口必须处于打开状态。MDN 上的 MessagePort
关于 start()
方法的描述是:
Starts the sending of messages queued on the port (only needed when using EventTarget.addEventListener; it is implied when using MessagePort.onmessage.)
这句话通过试验,能够理解为 start()
方法是与 addEventListener
配套使用的。若是咱们选择 onmessage
进行事件监听,那么将隐含调用 start()
方法。
// 主线程 var sharedWorker = new SharedWorker('shared-worker.js') sharedWorker.port.onmessage = function(e) { // 业务逻辑 }
var sharedWorker = new SharedWorker('shared-worker.js') sharedWorker.port.addEventListener('message', function(e) { // 业务逻辑 }, false) sharedWorker.port.start() // 须要显式打开
在传递消息时,postMessage()
方法和 onmessage
事件必须经过端口对象调用。另外,在 Worker 线程中,须要使用 onconnect
事件监听端口的变化,并使用端口的消息处理函数进行响应。
// 主线程 sharedWorker.port.postMessage([10, 24]) sharedWorker.port.onmessage = function (e) { console.log(e.data) } // Worker 线程 onconnect = function (e) { let port = e.ports[0] port.onmessage = function (e) { if (e.data.length > 1) { port.postMessage(e.data[1] - e.data[0]) } } }
能够在主线程中使用 terminate()
方法或在 Worker 线程中使用 close()
方法关闭 worker。这两种方法是等效的,但比较推荐的用法是使用 close()
,防止意外关闭正在运行的 Worker 线程。Worker 线程一旦关闭 Worker 后 Worker 将再也不响应。
// 主线程 worker.terminate() // Dedicated Worker 线程中 self.close() // Shared Worker 线程中 self.port.close()
能够经过在主线程或 Worker 线程中设置 onerror
和 onmessageerror
的回调函数对错误进行处理。其中,onerror
在 Worker 的 error
事件触发并冒泡时执行,onmessageerror
在 Worker 收到的消息不能进行反序列化时触发(本人通过尝试没有办法触发 onmessageerror
事件,若是在 worker 线程使用 postMessage
方法传递一个 Error 或 Function 对象会由于没法序列化优先被 onerror
方法捕获,而根本不会进入反序列化的过程)。
// 主线程 worker.onerror = function () { // ... } // 主线程使用专用线程 worker.onmessageerror = function () { // ... } // 主线程使用共享线程 worker.port.onmessageerror = function () { // ... } // worker 线程 onerror = function () { }
Web Worker 提供了 importScripts()
方法,可以将外部脚本文件加载到 Worker 中。
importScripts('script1.js') importScripts('script2.js') // 以上写法等价于 importScripts('script1.js', 'script2.js')
Worker 能够生成子 Worker,但有两点须要注意。
目前没有一类标签可使 Worker 的代码像 <script>
元素同样嵌入网页中,但咱们能够经过 Blob()
将页面中的 Worker 代码进行解析。
<script id="worker" type="javascript/worker"> // 这段代码不会被 JS 引擎直接解析,由于类型是 'javascript/worker' // 在这里写 Worker 线程的逻辑 </script> <script> var workerScript = document.querySelector('#worker').textContent var blob = new Blob(workerScript, {type: "text/javascript"}) var worker = new Worker(window.URL.createObjectURL(blob)) </script>
Web Worker 中,Worker 线程和主线程之间使用结构化克隆算法(The structured clone algorithm)进行数据通讯。结构化克隆算法是一种经过递归输入对象构建克隆的算法,算法经过保存以前访问过的引用的映射,避免无限遍历循环。这一过程能够理解为,在发送方使用相似 JSON.stringfy()
的方法将参数序列化,在接收方采用相似 JSON.parse()
的方法反序列化。
可是,一次数据传输就须要同时通过序列化和反序列化,若是数据量大的话,这个过程自己也可能形成性能问题。所以, Worker 中提出了 Transferable Objects
的概念,当数据量较大时,咱们能够选择在将主线程中的数据直接移交给 Worker 线程。值得注意的是,这种转移是完全的,一旦数据成功转移,主线程将不能访问该数据。这个移交的过程仍然经过 postMessage
进行传递。
postMessage(message, transferList)
例如,传递一个 ArrayBuffer 对象
let aBuffer = new ArrayBuffer(1) worker.postMessage({ data: aBuffer }, [aBuffer])
Worker 工做在一个 WorkerGlobalDataScope
的上下文中。每个 WorkerGlobalDataScope
对象都有不一样的 event loop
。这个 event loop
没有关联浏览器上下文(browsing context),它的任务队列也只有事件(events)、回调(callbacks)和联网的活动(networking activity)。
每个 WorkerGlobalDataScope
都有一个 closing
标志,当这个标志设为 true
时,任务队列将丢弃以后试图加入任务队列的任务,队列中已经存在的任务不受影响(除非另有指定)。同时,定时器将中止工做,全部挂起(pending)的后台任务将会被删除。
因为 Worker 工做的上下文不一样于普通的浏览器上下文,所以不能访问 window 以及 window 相关的 API,也不能直接操做 DOM。Worker 中提供了 WorkerNavigator
和 WorkerLocation
接口,它们分别是 window 中 Navigator
和 Location
的子集。除此以外,Worker 还提供了涉及时间、存储、网络、绘图等多个种类的接口,如下列举了其中的一部分,更多的接口能够参考 MDN 文档。