众所周知,Javascript是运行在单线程环境中,也就是说没有办法同时运行多个脚本,也就是说任务只能在一个线程里完成,一次只能做一件事。前面的任务没有结束,后面的任务就只能等着。举个例子,假设用户触发了一段需要一些时间才能完成Javascript代码,那么页面在这段代码执行完成之前,是没有办法响应用户的其他操作的。我们用实际代码来看一下现象。
我们用JavaScript写一个简单的计时器,代码如下:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>计时器</title>
</head>
<body>
<div class="time " ><span id="pastTime">00:00:00</span></div><br>
<hr>
<input type="button" id="logIn" value="确认" onclick="toDo()"></div>
<script>
var counter = 0;
setInterval(function () {
counter++;
var m=parseInt(counter/3000);
var s=parseInt((counter%3000)/50);
var ms=parseInt(counter%50);
document.getElementById("pastTime").innerHTML=toDub(m)+":"+toDub(s)+":"+toDub(ms);
},1000/50);
//补零
function toDub(n){
return n<10?"0"+n:""+n;
}
function toDo(){
if (confirm("继续?")){}
}
</script>
</body>
</html>
运行结果如下:
看起来没有问题,但是如果点一下“确认”按钮,问题出现了,这时候浏览器出现一个弹窗需要点击“确认”或者“取消”,也就是说JavaScript代码停在了这里等待用户做选择,这样,计时就被人为的暂停了下来。
那有没有办法不让计时器的运行被打断呢?答案就是使用Web Worker,将代码交给Web Worker去运行,浏览器会在后台启动一个独立的worker线程来专门负责计时器的运行,因此,页面在等待用户做选择的时间里计时器代码是不受用户操作影响的。
代码如下:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>计时器</title>
</head>
<body>
<div class="time " ><span id="pastTime">00:00:00</span></div><br>
<hr>
<input type="button" id="logIn" value="确认" onclick="toDo()"></div>
<script>
var counter = 0;
setInterval(function () {
var worker=new Worker("javascript/myjs.js");
worker.onmessage = function (event) {
counter = event.data.counter;
var m=parseInt(counter/3000);
var s=parseInt((counter%3000)/50);
var ms=parseInt(counter%50);
document.getElementById("pastTime").innerHTML=toDub(m)+":"+toDub(s)+":"+toDub(ms);
};
//补零
function toDub(n){
return n<10?"0"+n:""+n;
}
function toDo(){
if (confirm("继续?")){}
}
</script>
</body>
</html>
但是,如果将这段代码交给Web Worker去运行的话,那么情况就不一样了:浏览器会在后台启动一个独立的worker线程来专门负责这段代码的运行,因此,页面在这段Javascript代码运行期间依然可以响应用户的其他操作。
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<title>计时器</title>
</head>
<body>
<div class="time " ><span id="pastTime">00:00:00</span></div><br>
<hr>
<input type="button" id="logIn" value="确认" onclick="toDo()"></div>
<script>
var counter = 0;
var worker=new Worker("javascript/myjs.js");
worker.onmessage = function (event) {
counter = event.data.counter;
var m=parseInt(counter/3000);
var s=parseInt((counter%3000)/50);
var ms=parseInt(counter%50);
document.getElementById("pastTime").innerHTML=toDub(m)+":"+toDub(s)+":"+toDub(ms);
};
//补零
function toDub(n){
return n<10?"0"+n:""+n;
}
function toDo(){
if (confirm("继续?")){}
}
</script>
</body>
</html>
myjs.js代码:
var counter = 0;
setInterval(function () {counter++; postMessage({counter});},1000/50);
改成这样就可以解决“确认”对话框导致计时器停止的问题了。
使用Web Worker,注意一下几点:
- 通常情况下,Worker 载入的是一个单独的 JavaScript 脚本文件,如文中的myjs.js。
- Worker 线程无法读取主线程所在网页的 DOM 对象。在文中,把计时器时间写到HTML对应位置只能在主线程中完成,所以在等待“确认”过程中,计时器的值也是不会变化的,但是在Worker没有暂停计时,在“确认”完成后,计时器会直接跳到正确的值显示出来。
-
Worker 线程和主线程不能直接通信,需要通过消息完成。文中使用postMessage回传信息。
-
Worker 线程不能执行
alert()
方法和confirm()
方法。 -
Worker 线程可以使用 XMLHttpRequest 对象发出 AJAX 请求。