前言
在说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博客地址:http://blog.csdn.net/lecepin