1、简介
我们都知道JavaScript
这个语言在执行的时候是采用单线程进行执行的,也就是说在同一时间只能做一件事,这也和这门语言有很大的关系,采用同步执行的方式进行运行,如果出现阻塞,那么后面的代码将不会执行,HTML5则提出了web Worker标准,表示JavaScript
允许有多个线程,但是子线程完全受主线程的控制,切子线程不能操作DOM,只有主线程可以操作DOM,所以以主线程为主的单线程执行原理成了JavaScript
这门语言的核心。关于JavaScript
的运行机制可以参考阮一峰的文章JavaScript 运行机制详解:再谈Event Loop
2、Web Worker
下面我们来说说web worker
到底是什么,简单明了的一句话其实就是在Javascript
单线程执行的基础上,开启一个子线程,进行程序处理,而不影响主线程的执行,当子线程执行完毕之后再回到主线程上,在这个过程中并不影响主线程的执行过程。
举个例子:
传统情况下,执行下面的代码后,整个页面都会被冻结,由于javascript
是单线程处理,如下代码已经完全组塞了后续的执行
while(true){}
如果换一种方式,我们通过开启一个新的线程来执行这段代码,将他放在一个单独的worker.js
文件中,在主线程执行以下代码。
var worker = new Worker("worker.js")
创建线程
在创建线程的时候需要给实例化的Worker
传入唯一一个参数,指向一个javascript
文件资源的url或者Blob对象(Blob对象就是一个包含有只读原始数据类文件对象),调用这个构造函数之后,一个线程就被创建了,如下:
var worker = new Worker("worker.js");
var worker = new Worker(blob);
线程通信
Web Worker
的基本原理就是在当前的主线程中加载一个只读文件来创建一个新的线程,两个线程同时存在,且互不阻塞,并且在子线程与主线程之间提供了数据交换的接口postMessage
和onmessage
。来进行发送数据和接收数据。其数据格式可以为结构化数据(JSON
等);
当我们创建了一个worker实例之后,我们可以通过如下两种方式来发送数据:
var worker = new Worker("worker.js"); //实例化对象
//第一种传递方式
worker.postMessage(message,taransferList);
//第二种传递方式
worker.postMessage({
operation: "list_all_users",
//ArrayBuffer object
input: buffer,
threshold: 0.8,
}, [buffer]);
如果要想一个专用线程发送数据,那么我们需要使用线程中的 postMessage 方法。专用线程不仅仅支持传输二进制数据,也支持结构化的 JavaScript 数据格式。在这里有一点需要注意,为了高效地传输 ArrayBuffer 对象数据,需要在 postMessage 方法中的第二个参数中指定它。
同时我们如果需要接收某个线程传来的数据可以使用onmessage
来进行接收,方法如下:
//方法一
worker.onmessage = function(event){
var data = event.data; //通过event.data来获取传入的参数
}
//方法二
worker.addEventListener("message",target);
下面是一段运行在chrome中的参数传递方式:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webWorker</title>
</head>
<body>
<script>
var worker = new Worker("worker.js");
worker.postMessage("123456");
worker.onmessage = function (e) {
console.log(e.data)
};
</script>
</body>
</html>
worker.js
onmessage = function (e) {
console.log(e.data);
postMessage("2222")
};
此时我们的浏览器打印出的log是如下:
3、Worker基本使用
上面我们已经说了创建一个新的线程、传递数据、接收数据的方法,下面再次做一个精简的回顾。
创建新的Worker
var worker = new Worker("worker.js")
传递参数
worker.postMessage("text");
接收消息
worker.onmessage = function (e) {
var message = e.data;
};
异常处理
worker.onerror = function(e){
console.log("error at "+e.filename ":" + e.lineno + e.message)
}
结束worker
worker.terminate();
载入工具类函数
importScripts("./utils/base64.js","./utils/map.js"...)
需要注意的是importScripts
是同步方法,一旦importScripts
方法返回就可以开始使用载入的脚本,而不需要回调函数。
4、Worker作用域
当我们创建一个新的worker时,改代码会运行在一个全新的javascript的环境中(WorkerGlobalScope)运行,是完全和创建worker的脚本隔离,这时我们可以吧创建新worker的脚本叫做主线程,而被创建的新的worker叫做子线程。
WorkerGlobalScope是worker的全局对象,所以它包含所有核心javascript全局对象拥有的属性如JSON等,window的一些属性,也拥有类似于XMLHttpRequest()等。
但是我们所开启的新的worker也就是子线程,并不支持操作页面的DOM。
5、共享线程 SharedWorker
共享线程是为了避免线程的重复创建和销毁过程,降低了系统性能的消耗,共享线程SharedWorker
可以同时有多个页面的线程链接。
使用SharedWorker
创建共享线程,也需要提供一个javascript脚本文件的URL地址或Blob,该脚本文件中包含了我们在线程中需要执行的代码,如下:
var worker = new SharedWorker("sharedworker.js");
共享线程也使用了message
事件监听线程消息,但使用SharedWorker对象的port属性与线程通信如下。
worker.port.onmessage = function(e){
...
}
同时我们也可以使用SharedWorker对象的port属性向共享线程发送消息如下。
worker.port.postMessage("message");