执行结果如下:
script start
script endpromise1
promise2
setTimeout
跟你想的一样嘛?为什么会是这样的结果?听我细细道来。
正如前面的文章所提到,js是单线程的,每个线程有它自己的唯一的事件循环,事件循环的任务源可以不唯一。类似setTimeout, promise, ajax, DOM操作等都是典型的任务源,任务队列中的任务便是来自这些任务源。而这些任务源产生的任务又可以分为Tasks和Microtasks两种。
tasks
tasks中的任务都是有时间顺序的,因此浏览器能够有序地从中调度任务并执行。在任务与任务之间,浏览器可能会渲染更新。
tasks中一个典型就是setTimeout,setTimeout函数等待给定的延迟事件然后将其回调函数推入Tasks中。这就是为什么先输出'script end' 后输出'setTimeout'的原因。
tasks主要有:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
microtasks
microtasks中的任务在当前函数调用栈中的函数执行完成之后即调度,像promise、mutation都会被推入microtasks队列中。并且microtasks中的一个任务执行完成后,后续的microtask也会继续执行,直到Microtasks为空,这就解释了为什么promise2也会在setTimeout之前输出的原因。
micro-tasks主要有process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
当tasks队列中的一个任务时,如果函数调用栈为空,便会开始执行microtasks中的任务,直至microtasks中所有任务执行完毕,然后event loop才会继续执行tasks中的下一个任务。
准备好接受一个更加复杂的例子嘛?Let's go!
<div class='outer'>
<div class='inner'></div></div>
// Let's get hold of those elementsvar outer = document.querySelector('.outer');var inner = document.querySelector('.inner');//Listen for attribute changes on the outer elementnew MutationObserver(function() { console.log('mutate')
}).observe(outer, { attributes: true});function onClick() { console.log('click');
setTimeout(function() { console.log('timeout');
}, 0); Promise.resolve().then(function() { console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
输出结果如下:
click
promise
mutate
click
promise
mutate
timeout
timeout
上述例子中Dispatch click和setTimeout属于tasks,对应的回调函数被推进tasks队列中,Mutation observer和promise属于Microtasks,对应的回调函数则被推入Microtasks队列中。当点击inner元素时,代码执行执行过程如下所示
Dispatch click被推入tasks中,当点击inner元素时onClick被推入函数调用栈中,执行上下文进入onClick中,将setTimeout的回调函数推入tasks队列中,Mutation observers和Promise then的回调函数推入microtasks队列中,并执行输出click。

1.jpg
2.当onClick执行结束后, 函数调用栈为空,将promise then 的回调函数推入函数调用栈

2.jpg
同样地,当promise then的回调函数执行结束后,将mutation observers的回调函数推入函数调用栈

绘图3.jpg
由于事件冒泡机制,父元素outer也会响应点击事件,因此重复1-3步骤,执行结束后如下所示

绘图4.jpg
此时函数调用栈和microtasks中均为空,因此event loop将执行tasks中的下一个任务

绘图5.jpg
再执行tasks中的最后一个任务

绘图6.jpg
整个过程大致如上所示,通过这个梨子,可以比较清晰地掌握event loop调用任务的顺序。
以上例子均在谷歌浏览器中的输出结果,不同浏览器有所差异,以谷歌浏览器为准!
作者:小道小姐姐
链接:https://www.jianshu.com/p/dda597d7fb3c