JS事件循环模型

Javascript的单线程机制。在现有浏览器环境中,Javascript执行引擎是单线程的,主线程的语句和方法,会阻塞定时任务的运行,执行引擎只有在执行完主线程的语句后,定时任务才会实际执行,这期间的时间,可能大于注册任务时设置的延时时间。在这一点上,Javascript与Java/C#的机制很不同。

在单线程的Javascript引擎中,setTimeout()是如何运行的呢,这里就要提到浏览器内核中的事件循环模型了。简单的讲,在Javascript执行引擎之外,有一个任务队列,当在代码中调用setTimeout()方法时,注册的延时方法会交由浏览器内核其他模块(以webkit为例,是webcore模块)处理,当延时方法到达触发条件,即到达设置的延时时间时,这一延时方法被添加至任务队列里。这一过程由浏览器内核其他模块处理,与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,会从任务队列中顺序获取任务来执行,这一过程是一个不断循环的过程,称为事件循环模型。

console.log方法是一个webkit内核支持的方法,在执行引擎执行这个代码的时候,console.log会先被入栈,然后立即执行直接出栈,而不被其他方法阻塞。而setTimeout()方法属于事件循环模型WebAPIs中的方法。引擎将方法出栈执行的时候,直接将回调函数交给了相应的模块。待时间到达以后再将函数交回任务队列,等到执行栈被清空之后,再将延时方法送入执行栈中。

Webkit中的Timer实现

TimerHeap采用最小堆的数据结构,预期延时时间最小的任务最先被执行,同时,预期延时时间相同的两个任务,其执行顺序是按照注册的先后顺序执行。

循环中的 setTimeout()
循环中的代码会按照代码块的执行规则来运行,setTimeout将会在每次循环结束时执行。对于for循环,i++的操作将在每次循环执行之后来执行。

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<body onload="countDown()">
<p id="clock"></p>

<script type="text/javascript">
function countDown() {
var now = new Date();
var h = now.getHours();
var m = now.getMinutes();
var s = now.getSeconds();
document.getElementById("clock").innerHTML = "now is:"+h+":"+m+":"+s;
setTimeout("countDown()", 1000);
}
</script>
</body>
</html>

问题:为什么用递归方式?有什么好处?

  1. 准确,因为是一直在拿最新时间。不会出现使用减减时执行引擎的卡顿导致任务队列中任务不能进入执行线程造成的不准确现象。
  2. 可以方便的暂停,通过setTimeout()获得的句柄,使用clearTimeout(no)来暂停计时。