PWA(Progressive Web App)入门系列:(五)Web Worker

922 阅读7分钟

前言

在说Service Worker前有必要说一下Web Worker,因为Service Worker本身就属于Web Worker的延伸,大部分功能也是基于Web Worker进行的扩展。

背景

众所周知,JavaScript引擎是以单线程调度的方式进行,我们无法同时运行多个JavaScript文件,这种情况下就会导致对硬件资源无法充分利用,并且当在进行一些高耗性能的操作时,会影响主线程的其他任务,造成任务阻塞及用户体验差等问题。

在这种劣势情况下,到 2008 年 W3C 提出第一个 HTML5 草案开始,就在 HTML5 中提出了Web Worker的概念,并规范了Web Worker的三大特征:

  • 能够长时间运行
  • 理想的启动性能
  • 理想的内存消耗

简介

Web Worker 是HTML5标准的一部分,这一规范定义了一套 API。实现了 用Web Worker 来实现 JavaScript 的 “多线程” 技术,并发执行多个 JavaScript 脚本。

Web Worker 与传统多线程

每个JavaScript脚本执行流都称为一个线程,彼此之间互相独立,并且有浏览器中的 JavaScript 引擎负责管理,当然这并不是说JavaScript支持多线程,虽然传统JavaSript有多种方式实现了对多线程的模拟(例如:setinterval,setTimeout,以及一些异步的操作方法等),但是在本质上程序的运行仍然是由 JavaScript 引擎以单线程调度的方式运行的,而Web Worker的线程是依赖于浏览器(宿主环境)来实现的,从而实现了对浏览器端多线程编程的支持。

Web Worker 线程种类

Web Worker 有两种不同线程类型,分别是:

  • Dedicated Worker (专用线程)。只能被首次生成它的脚本使用
  • Shared Worker (共享线程)。可以同时被多个脚本使用

通常来说的Web Worker指的就是Dedicated Worker,Service Worker也属于其中,并且各大浏览器对其支持良好,而Shared Worker指的是SharedWorker,目前各大浏览器对其支持度较差。

这里主要对Dedicated Worker进行详细说明,对于Shared Worker不再进行细说。

Worker模式



Worker线程执行流



创建 Web Worker

下面说一下如何创建一个Web Worker

语法:

new Worker(
  in DOMString aStringURL
);

使用上面的方式即可以创建一个Web Worker对象,它执行的是aStringURL中的脚本。目前大多数浏览器是支持data URI的aStringURL的,可以通过URL.createObjectURL(blob)创建。但需要注意的是脚本必须遵循同源策略。

下面创建一个Worker,Worker的执行脚本是workerfile.js,创建成功后,它会返回一个新的Worker对象赋值给前面声明的workerObj变量

var workerObj = new Worker('./workerfile.js');

这里需要注意, worker线程的创建的是异步的,主线程代码不会阻塞在这里去等待worker线程去加载、执行相应的脚本文件,而是会立即向下执行后面代码。

Web Worker实例方法

Worker的实例方法只有两个:

  • postMessage
  • terminate

postMessage

主线程向生成的Worker线程发送数据的方法。

语法:

workerObj.postMessage(aMessage, transferList);
  • aMessage:向Worker线程发送的消息数据对象。它可以是任何类型的值或JavaScript对象。
  • transferList:可选。Transferable类型的数组。主要用在 ArrayBuffer, MessagePort, ImageBitmap对象。

注意:

postMessage发送的aMessage参数,在传递通讯的时候会对数据进行克隆,为了防止多个线程间的数据同时修改的问题。实际上,浏览器内部的实现是,先将通信传递的数据串行化,随后把串行化后的数据发给子线程,后者再将数据还原。

postMessage也可以以二进制的方式传输,例如 ArrayBuffer 、File、Blob、ImageBitmap等对象。但是往往传输的这些对象数据量都很大,前面说了传输数据会进行拷贝,如果传一个100MB的数据,那么浏览器默认会再复制一份100MB的数据,导致一些不必要的资源消耗。为了防止这种问题,就可以使用上面说的第二个参数transferList来解决。

顺道科普一下Transferable接口。这个接口代表一个能在不同可执行上下文中相互传递的对象,例如主线程和Worker线程。

var arrBuff = new ArrayBuffer(8);
myWorker.postMessage(arrBuff, [arrBuff]);

terminate

语法:

workerObj.terminate()

用于立即终止worker对象的行为,如果worker正在运行着任务也会立即终止。

Web Worker实例属性

Worker实例包含两个属性:

  • onmessage:用来接收worker线程传递过来的数据事件。
  • onerror:用来接收worker线程的错误信息。

onmessage

onmessage属性表示一个EventHandler事件处理函数,当Worker子线程返回一条消息时被调用。

语法:

workerObj.onmessage = function(e) { 
    ... 
}

传递来的消息被封装在事件的data属性中。

workerObj.onmessage = function(e) {
  var result = e.data;
}

onerror

onerror属性是EventListener 一个事件监听函数,一旦有类型为 error 的 ErrorEvent 从 worker线程中冒泡出来时就会执行该函数。可以通过preventDefault()来取消冒泡。

主要用到的错误属性有:

  • message: 可读的错误信息
  • filename: 发生错误的脚本文件名称
  • lineno: 发生错误的脚本所在文件的行数

Web Worker文件方法

Worker线程对象抽象于DedicatedWorkerGlobalScope接口。此作用域下没有window对象,需要用self来调用。

在发送数据和接收数据使用的方法和worker实例对象的一样:

  • postMessage
  • onerror

这两个方法就不说了,还有一个close方法说一下。

close

这个和terminate()有点类似。这个方法主要用来清除所有在WorkerGlobalScope事件环中的排队任务,关闭特定作用域。

self.close()

importScripts 导入脚本

WorkerGlobalScope 对象中可以使用importScripts()方法来进行对脚本文件和资源的引入。

但这个操作需要注意:

  • 如果没有给 importScripts 方法任何参数,那么立即返回,终止下面的步骤。
  • 解析 importScripts 方法的每一个参数。
  • 如果有任何失败或者错误,抛出 SYNTAX_ERR 异常。
  • 尝试从用户提供的 URL 资源位置处获取脚本资源。
  • 对于 importScripts 方法的每一个参数,按照用户的提供顺序,获取脚本资源后继续进行其它操作。

Worker线程声明周期

worker线程间的数据传递必须依赖于浏览器的context环境,通过MessagePort进行传递数据,所以每个worker线程的全局作用域都会有端口列表,并且会在WorkerGlobalScope中生成一个worker线程的线程列表,在初始化时为空。当worker线程创建时会被填充进去,当worker线程终止时会从这个列表删除。

worker线程中可调用的对象

在worker线程中,可以获得下列对象:

  • navigator
  • location
  • XMLHttpRequest
  • setTimeout/setInterval
  • Application Cache
  • fetch
  • atob/btoa

等等。

实例

下面写一个使用的小例子

html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<button id="btn">发送</button>
<script>
    var worker = new Worker('./worker.js')

    btn.onclick = function(){
        worker.postMessage({a:1,b:2,c:3})
    }

    worker.onmessage = function(e){
        console.log('index-msg:', e)
    }

    worker.onerror = function(e) {
        console.log('index-err', e)
        e.preventDefault()
    }
</script>   
</body>
</html>

worker.js文件

self.onmessage = function(e) {
    console.log('worker in:', e)

    self.postMessage('get postMessage!')
}

兼容性

还是有必要列一下Worker目前在浏览器上的兼容性:



可以看到支持的非常不错。

总结

可以看到Web Worker的出现使得在 Web 进行多线程编程成为可能,对于高消耗、耗时长的操作可以放到woker里面去进行。

所以可以在以下应用场景使用:

  • 使用专用线程进行数学运算
  • 图像处理
  • 大量数据的检索
  • 背景数据分析

等。


博客名称:王乐平博客

CSDN博客地址:blog.csdn.net/lecepin

知识共享许可协议 本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。