首先看一段小程序:
<script>
alert('第1');
setTimeout(function(){alert('第2');}, 2000);
alert('第3');
</script>
输出顺序是:第1,第3,第2;再来看一段小程序:
<script>
alert('1');
setTimeout(function(){alert('第2')}, 0);
alert('3');
</script>
输出顺序还是:第1,第3,第2;你可能会问为什么不是1,2,3;setTimeout的间隔时间设置为0不就是立即执行吗?呵呵。在此引出重点——JavaScript的运行机制:Event Loop。
JavaScript的任务队列
因为JavaScript是单线程的,所有的任务只能是一个接一个的执行,但是当遇像IO这样的读取一些大文件时就会出现后一个任务长时间处于等待状态,要等到前一个任务执行完才能开始后一个任务。
因为这样,JavaScript也设计为主线程可以先不管IO设备,先执行后面的任务,等IO设备返回了结果,再回去继续执行。
因此,在JavaScript中,所有的任务可以分为两种:一是同步任务(synchronous);二是异步任务(asynchronous)。同步任务就是:在主线程上,必须等前一个任务执行完,才能执行后一个任务;异步任务则是:不进入主线程,而是进入“任务队列”(task queue)的任务,主线程只有在得到“任务队列”的通知,某个异步任务可以执行,该异步任务才会进入主线程执行。
其实“任务队列”就是一个事件的队列,主线程读取“任务队列”,就是读取里面的事件。这些事件一般包括IO设备的事件、点击、滚动事件,凡是指定过回调函数(callback),这些事件发生时就会进入“任务队列”。异步任务必须指定回调函数,主线程执行异步任务,就是执行对应的回调函数。
oText.innerText = 'something';
比如在某一页面中,执行了上面这一代码后,DOM的内容会发生改变,接着系统触发DOM Changed事件,产生异步回调,回调函数被添加到“任务队列”中。
Event Loop:
1.同步任务在主线程上执行会形成一个“执行栈”(execution context stack);
2.异步任务在“任务队列”里有运行结果就会在“任务队列”里置一事件(Event);
3.当“执行栈”中的同步任务都执行完后,“任务队列”中有运行结果(事件,比如:Mouse Clicks、Key Presses、定时事件等)的异步任务就会进入“执行栈”,开始执行;
主线程从“任务队列”读取事件的过程是不断循环的,这种机制就称为Event Loop。
Event Loop图解:
只要“执行栈”中任务执行完了,就会去读取“任务队列”,执行各事件相对应的回调函数。
现在应该明白开篇中,为什么setTimeout的间隔时间设置为0却最后执行了吧,因为要等“执行栈”中的代码执行完后,才会去执行“任务队列”中的回调函数。(这也是为什么有些书上会写有时setTimeout()里的回调函数执行的间隔时间不是后面设置的值)