web worker
# web worker
在运行大型或者复杂的 JavaScript 脚本的时候经常会出现浏览器假死的现象,这是由于 JavaScript 这个语言在执行的时候是采用单线程来执行而导致的。采用同步执行的方式进行运行,如果出现阻塞,那么后面的代码将不会执行。例如:
while(true){}
那么能不能让这些代码在后台运行,或者让 JavaScript 函数在多个进程中同时运行呢?
HTML5 所提出的 Web Worker 正是为了解决这个问题而出现的。
HTML5 的 Web Worker 可以让 Web 应用程序具备后台的处理能力。它支持多线程处理功能,因此可以充分利用多核 CPU 带来的优势,将耗时长的任务分配给 HTML5 的 Web Worker 运行,从而避免了有时页面反应迟缓,甚至假死的现象。
本文将分为以下几个部分来介绍 web worker:
- web worker 概述
- web Worker 使用示例
- 使用 web Worker 实现跨标签页通信
# web worker 概述
在 Web 应用程序中,web Worker 是一项后台处理技术。
在此之前,JavaScript 创建的 Web 应用程序中,因为所有的处理都是在单线程内执行的,所以如果脚本所需运行时间太长,程序界面就会长时间处于停止状态,甚至当等待时间超出一定的限度,浏览器就会进入假死的状态。
为了解决这个问题,HTML5 新增加了一个 web Worker API。
使用这个 API,用户可以很容易的创建在后台运行的线程,这个线程被称之为 Worker。如果将可能耗费较长时间的处理交给后台来执行,则对用户在前台页面中执行的操作没有影响。
web Worker 的特点如下:
通过加载一个 JS 文件来进行大量复杂的计算,而不挂起主进程。通过 postMessage 和 onMessage 进行通信。
可以在 Worker 中通过 importScripts(url) 方法来加载 JavaScript 脚本文件。
可以使用 setTimeout( ),clearTimeout( ),setInterval( ) 和 clearInterval( ) 等方法。
可以使用 XMLHttpRequest 进行异步请求。
可以访问 navigator 的部分属性。
可以使用 JavaScript 核心对象。
除了上述的优点,web Worker 本身也具有一定局限性的,具体如下:
不能跨域加载 JavaScript
Worker 内代码不能访问 DOM
使用 Web Worker 加载数据没有 JSONP 和 Ajax 加载数据高效。
目前来看,web Worker 的浏览器兼容性还是很不错的,基本得到了主流浏览器的一致支持。
在开始使用 web Worker 之前,我们先来看一下使用 Worker 时会遇到的属性和方法,如下:
self:self 关键值用来表示本线程范围内的作用域。
postMessage( ):向创建线程的源窗口发送信息。
onMessage:获取接收消息的事件句柄。
importScripts(urls):Worker 内部如果要加载其他脚本,可以使用该方法来导入其它 JavaScript 脚本文件。参数为该脚本文件的 URL 地址,导入的脚本文件必须与使用该线程文件的页面在同一个域中,并在同一个端口中。
例如:
// 导入了 3 个 JavaScript 脚本
importScripts("worker1.js","worker2.js","worker3.js");
# web Worker 使用示例
接下来我们来看一下 web Worker 的具体使用方式。
web Worker 的使用方法非常简单,只需要创建一个 web Worker 对象,并传入希望执行的 JavaScript 文件即可。
之后在页面中设置一个事件监听器,用来监听由 web Worker 对象发来的消息。
如果想要在页面与 web Worker 之间建立通信,数据需要通过 postMessage( ) 方法来进行传递。
创建 web Worker。步骤十分简单,只要在 Worker 类的构造器中,将需要在后台线程中执行的脚本文件的 URL 地址作为参数传入,就可以创建 Worker 对象,如下:
var worker = new Worker("./worker.js");
注意:在后台线程中是不能访问页面或者窗口对象的,此时如果在后台线程的脚本文件中使用 window 或者 document 对象,则会引发错误。
这里传入的 JavaScript 的 URL 可以是相对或者绝对路径,只要是相同的协议,主机和端口即可。
如果想获取 Worker 进程的返回值,可以通过 onmessage 属性来绑定一个事件处理程序。如下:
var worker = new Worker("./worker.js");
worker.onmessage = function(){
console.log("the message is back!");
}
这里第一行代码用来创建和运行 Worker 进程,第 2 行设置了 Worker 的 message 事件,当后台 Worker 的 postMessage( ) 方法被调用时,该事件就会被触发。
使用 Worker 对象的 postMessage( ) 方法可以给后台线程发送消息。发送的消息需要为文本数据,如果要发送任何 JavaScript 对象,需要通过 JSON.stringify( ) 方法将其转换成文本数据。
worker.postMessage(message);
通过获取 Worker 对象的 onmessage 事件以及 Worker 对象的 postMessage( ) 方法就可以实现线程之间的消息接收和发送。
Web Worker 不能自行终止,但是能够被启用它们的页面所终止。
调用 terminate( ) 函数可以终止后台进程。被终止的 Web Workers 将不再响应任何消息或者执行任何其他运算。
终止之后,Worker 不能被重新启动,但是可以使用同样的 URL 创建一个新的 Worker。
下面是 web Worker 的一个具体使用示例。
index.html
<p>计数:<output id="result"></output></p>
<button id="startBtn">开始工作</button>
<button id="stopBtn">停止工作</button>
var startBtn = document.getElementById("startBtn");
var stopBtn = document.getElementById("stopBtn");
var worker; // 用于存储 Worker 进程
// 开始 Worker 的代码
startBtn.onclick = function () {
// 第一次进来没有 Worker 进程 , 创建一个新的 Worker 进程
worker = new Worker("worker.js");
// 接收来自于后台的数据
worker.onmessage = function (event) {
document.getElementById("result").innerHTML = event.data;
};
}
// 停止 Worker 的代码
stopBtn.onclick = function () {
worker.terminate();
worker = undefined;
}
worker.js
var i = 0;
function timedCount() {
i++;
// 每次得到的结果都通过 postMessage 方法返回给前台
postMessage(i);
setTimeout("timedCount()", 1000);
}
timedCount();
在上面的代码中,当用户点击"开始工作"时,会创建一个 Web Worker 在后台进行计数。每次计的数都会通过 postMessage( ) 方法返回给前台。
当用户点击"停止工作"时,则会调用 terminate( ) 方法来终止 Web Worker 的运行。
# 使用 web Worker 实现跨标签页通信
web Worker 可分为两种类型:
专用线程 dedicated web worker
共享线程 shared web worker
Dedicated web worker 随当前页面的关闭而结束,这意味着 Dedicated web worker 只能被创建它的页面访问。
与之相对应的 Shared web worker 共享线程可以同时有多个页面的线程链接。
前面我们示例 web Worker 时,实例化的是一个 Worker 类,这就代表是一个 Dedicated web worker,而要创建 SharedWorker 则需要实例化 SharedWorker 类。
var worker = new SharedWorker("sharedworker.js");
下面我们就使用 Shared web worker 共享线程来实现跨标签页通信。
index.html
<body>
<input type="text" name="" id="content" placeholder="请输入要发送的信息">
<button id="btn">发送</button>
<script>
const content = document.querySelector("#content");
const btn = document.querySelector("#btn");
const worker = new SharedWorker('worker.js')
btn.onclick = function () {
worker.port.postMessage(content.value);
}
</script>
</body>
index2.html
<body>
<script>
const btn = document.querySelector("#btn");
var worker = new SharedWorker('worker.js');
worker.port.start()
worker.port.addEventListener('message', (e) => {
if(e.data){
console.log('来自worker的数据:', e.data)
}
}, false);
setInterval(function(){
// 获取和发送消息都是调用 postMessage 方法,我这里约定的是传递'get'表示获取数据。
worker.port.postMessage('get')
},1000);
</script>
</body>
worker.js
var data = '';
onconnect = function (e) {
var port = e.ports[0]
port.onmessage = function (e) {
// 如果是 get 则返回数据给客户端
if (e.data === 'get') {
port.postMessage(data);
data = "";
} else {
// 否则把数据保存
data = e.data
}
}
}
-EOF-