JavaScript 语言特点
- 单线程,而这个线程中拥有唯一的一个事件循环。(web worker 这里不参与讨论)
- 代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。
- 一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
- 任务队列又分为 macro-task(宏任务)与 micro-task(微任务),在最新标准中,它们被分别称为 task 与 jobs 。
- setTimeout/Promise 等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。
// setTimeout 中的回调函数才是进入任务队列的任务
setTimeout(function() {
console.log('xxxx');
})
- 来自不同任务源的任务会进入到不同的任务队列。其中 setTimeout 与 setInterval 是同源的。
- 事件循环的顺序,决定了 JavaScript 代码的执行顺序。它从 script (整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的 micro-task 。当所有可执行的 micro-task 执行完毕之后。循环再次从 macro-task 开始,找到其中一个任务队列执行完毕,然后再执行所有的 micro-task,这样一直循环下去。
- 其中每一个任务的执行,无论是 macro-task 还是 micro-task,都是借助函数调用栈来完成。
macrotask 与 microtask 类别具体分类
// macrotasks
script(整体代码), setImmediate, setTimeout, setInterval, I/O, UI rendering
// microtasks
process.nextTick, Promises, Object.observe, MutationObserver
DEMO
setImmediate(function () {
console.log();
}, );
setTimeout(function () {
console.log();
}, );
new Promise(function (resolve) {
console.log();
resolve();
console.log();
}).then(function () {
console.log();
});
console.log();
process.nextTick(function () {
console.log();
});
console.log();
// 3 4 6 8 7 5 1 2
执行过程如下:
- JavaScript引擎首先会从macrotask queue中取出第一个任务,即 script (整段代码)
- 执行完毕后,将microtask queue中的所有任务取出,按顺序全部执;
-
然后再从macrotask queue中取下一个,
执行完毕后,
-
再次将microtask queue中的全部取出;
循环往复,直到两个queue中的任务都取完。
解释:
- 代码开始执行时,所有这些代码在
中,取出来执行之。macrotask queue
- 后面遇到了
,又加入到setTimeout
中,macrotask queue
- 然后,遇到了
,放入到了另一个队列promise.then
。microtask queue
- 整个
执行完后,execution context stack
- 取
中的任务了。microtask queue
因此
promise.then
的回调比
setTimeout
先执行。
参考
Event loops
question
Q:
process.nextTick
也会放入
microtask quque
,为什么优先级比
promise.then
高呢?
A:process.nextTick 永远大于 promise.then
在 Node 中,_tickCallback 在每一次执行完 TaskQueue 中的一个任务后被调用,而这个 _tickCallback 中实质上干了两件事:
1. nextTickQueue中所有任务执行掉(长度最大1e4,Node版本v6.9.1)
2. 第一步执行完后执行
_runMicrotasks
函数,执行
microtask
中的部分(
promise.then
注册的回调)所以很明显
process.nextTick > promise.then
”