技术背景
家喻户晓,JavaScript 语言采纳的是单线程模型,也就是说,所有工作只能在一个线程上实现,一次只能做一件事。后面的工作没做完,前面的工作只能等着。随着电脑计算能力的加强,尤其是多核 CPU 的呈现,单线程带来很大的不便,无奈充分发挥计算机的计算能力。
Web Worker 的作用,就是为 JavaScript 发明多线程环境,容许主线程创立 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后盾运行,两者互不烦扰。等到 Worker 线程实现计算工作,再把后果返回给主线程。这样的益处是,一些计算密集型或高提早的工作,被 Worker 线程累赘了,主线程(通常负责 UI 交互)就会很晦涩,不会被阻塞或拖慢。
Worker 线程一旦新建胜利,就会始终运行,不会被主线程上的流动(比方用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信。然而,这也造成了 Worker 比拟消耗资源,不应该适度应用,而且一旦应用结束,就应该敞开。
应用限度
1、同源限度
调配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。所谓同源指同 ip,同协定,同端口。不能像 script 那样无限度。
2、DOM 限度
Worker 线程所在的全局对象,与主线程不一样,无奈读取主线程所在网页的 DOM 对象,也无奈应用 document、window、parent 这些对象。然而,Worker 线程能够 navigator 对象和 location 对象,也能够拜访 XMLHttpRequest。
3、通信限度
因为是不同的脚本环境,Worker 和主线程它们不能间接通信,必须通过音讯实现。
4、文件限度
Worker 线程无奈读取本地文件,即不能关上本机的文件系统(file://),它所加载的脚本,必须来自网络。
根本用法
主线程
主线程采纳 new 命令,调用 Worker() 构造函数,新建一个 Worker 线程。
Worker() 构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的工作。因为 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有胜利(比方 404 谬误),Worker 就会默默地失败。
而后,主线程就调用 worker.postMessage() 办法,向 Worker 发消息。
worker.postMessage() 办法的参数,就是主线程传给 Worker 的数据。它能够是各种数据类型,包含二进制数据。
接着,主线程通过 worker.onmessage 指定监听函数,接管 worker 子线程发回来的音讯。
下面代码中,事件对象的 data 属性能够获取 Worker 发来的数据。
Worker 实现工作当前,主线程就能够把它关掉。
Worker 线程
Worker 线程外部须要有一个监听函数,监听 message 事件。
除了应用 self.addEventListener() 指定监听函数,也能够应用 self.onmessage 指定。监听函数的参数是一个事件对象,它的 data 属性蕴含主线程发来的数据。self.postMessage() 办法用来向主线程发送音讯。
依据主线程发来的数据,Worker 线程能够调用不同的办法,上面是一个例子。
下面代码中,self.close() 用于在 Worker 外部敞开本身。
Worker API
主线程
浏览器原生提供 Worker() 构造函数,用来供主线程生成 Worker 线程。
const myWorker = new Worker(workUrl, [options] );
Worker() 构造函数,能够承受两个参数。第一个参数是脚本的网址(必须恪守同源政策),该参数是必须的,且只能加载 JS 脚本,否则会报错。第二个参数是配置对象,该对象可选。
Worker() 构造函数返回一个 Worker 线程对象,用来供主线程操作 Worker。Worker 线程对象的属性和办法如下。
Worker.onerror // 指定 error 事件的监听函数。Worker.onmessage // 指定 message 事件的监听函数,发送过去的数据在 Event.data 属性中。Worker.onmessageerror // 指定 messageerror 事件的监听函数。发送的数据无奈序列化成字符串时,会触发这个事件。Worker.postMessage() // 向 Worker 线程发送音讯。Worker.terminate() // 立刻终止 Worker 线程。
Worker 线程
Web Worker 有本人的全局对象,不是主线程的 window,而是一个专门为 Worker 定制的全局对象。因而定义在 window 下面的对象和办法不是全副都能够应用。
Worker 线程有一些本人的全局属性和办法。
self.name // Worker 的名字。该属性只读,由构造函数指定。self.onmessage // 指定 message 事件的监听函数。self.onerror // Worker 出错时触发
self.onmessageerror // 指定 messageerror 事件的监听函数。发送的数据无奈序列化成字符串时,会触发这个事件。self.close() // 敞开 Worker 线程。self.postMessage() // 向产生这个 Worker 线程发送音讯。self.importScripts() // 加载 JS 脚本。
对于 worker importScripts Api 是指 worker 自身能够加载其它的 js 脚本,如
importScripts(path0)
importScripts(path0, path1)
importScripts(path0, path1, /* … ,*/ pathN)
数据通信
后面说过,主线程与 Worker 之间的通信内容,能够是文本,也能够是对象,也能够是二进制。须要留神的是,这种通信是拷贝关系,即是传值而不是传址,Worker 对通信内容的批改,不会影响到主线程。事实上,浏览器外部的运行机制是,先将通信内容串行化,而后把串行化后的字符串发给 Worker,后者再将它还原。
上面是一个例子。
然而,拷贝形式发送二进制数据,会造成性能问题。比方,主线程向 Worker 发送一个 500MB 文件,默认状况下浏览器会生成一个原文件的拷贝。为了解决这个问题,JavaScript 容许主线程把二进制数据间接转移给子线程,然而一旦转移,主线程就无奈再应用这些二进制数据了,这是为了防止出现多个线程同时批改数据的麻烦场面。这种转移数据的办法,叫做 Transferable Objects。这使得主线程能够疾速把数据交给 Worker,对于影像解决、声音解决、3D 运算等就十分不便了,不会产生性能累赘。
如果要间接转移数据的控制权,就要应用上面的写法。
// Transferable Objects 格局
worker.postMessage(arrayBuffer, [arrayBuffer]);
// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);
同页面的 Web Worker
通常状况下,Worker 载入的是一个独自的 JavaScript 脚本文件,然而也能够载入与主线程在同一个网页的代码。
场景一:
<!DOCTYPE html>
<body>
<script id="worker" type="app/worker">
addEventListener('message', function () {postMessage('some message');
}, false);
</script>
</body>
</html>
/* 下面是一段嵌入网页的脚本,留神必须指定 <script> 标签的 type 属性是一个浏览器不意识的值,上例是 app/worker。而后,读取这一段嵌入页面的脚本,用 Worker 来解决。*/
var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
worker.onmessage = function (e) {// e.data === 'some message'};
场景二:
function createWorker(f) {var blob = new Blob(['(' + f.toString() +')()']);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);
return worker;
}
var pollingWorker = createWorker(function (e) {
var cache;
function compare(new, old) {...};
setInterval(function () {fetch('/my-api-endpoint').then(function (res) {var data = res.json();
if (!compare(data, cache)) {
cache = data;
self.postMessage(data);
}
})
}, 1000)
});
pollingWorker.onmessage = function () {// render data}
pollingWorker.postMessage('init');
Woker 的嵌套
Worker 线程外部还能再新建 Worker 线程。