setTimeout
这样一个函数大家一定不会陌生,但是对于setTimeout
这样看起来很简单的东西,却有一个细节相对比较容易忽视。我们来看一下,对setTimeout
的描述通常像这样:给定一个回调及n毫秒的延迟,setTimeout
就会在n毫秒后运行该回调。但实际上,这个描述是存在严重缺陷的,只能说在多数情况下,该描述只能算接近正确,但在其他某些情况下,则会出现严重的错误。
首先,我们来看一个简单的例子,该例子常常会迷惑JavaScript新手:
|
|
大家认为运行结果的打印顺序会是先1再2,还是先2再1呢?
按照定式思维,setTimeout
此时定义了0毫秒延迟
后执行f1
函数,所以应该是立即执行,打印出1,然后再执行下一条语句打印出2。
但实验之后,我们会发现,结果是先2再1。那在这个过程中,setTimeout
究竟都干了些什么呢?
要想真正理解setTimeout
,我们必须先大致了解JavaScript事件模型。
所有的JavaScript代码都由JavaScript脚本运行时引擎(Runtime),也叫JavaScript解释器
来负责解释。在JavaScript解释器
调用setTimeout
的时候,浏览器的timer模块
会进行延时处理,当时间到达的时候(如本例中为0毫秒后),就会产生一个事件排入事件队列
(事件队列由浏览器负责维护),setTimeout
会立刻返回。setTimeout
做的事情就是产生一个事件,然后被放到事件队列
里面,此时f1函数
根本不会被执行。JavaScript解释器
会直接执行下一行代码,直到出现空闲没有任何代码了,这时浏览器才会检查事件队列。如果队列中有事件,则浏览器会挑选出排在最前面的事件,并把此事件的处理器移到JavaScript解释器
去执行。事件处理器返回后,我们又回到队列处,继续取出下一个事件的处理器到JavaScript解释器
去执行。
所以,console.log("2")
在没有被执行之前,f1函数
不可能被执行,即使setTimeout
中的延时为0毫秒。
如果JavaScript解释器一直不空闲,那事件队列中的事件就永远不会被触发,就像下面例子这样:
由于出现了一个while无限循环,造成了线程的阻塞,躺在事件队列里面的f1
将永远无法执行。所以输出结果中只会看见2。
|
|
这样一个例子为什么会出现这样的输出结果而不是1 2 3
,现在就不难理解了吧。同样的缘故,在for循环没有结束之前,事件队列中的function函数是不会被执行的。
所以说,setTimeout
和setInterval
一样,它们的异步机制决定了其计时精准度严格来说是不准确的。