阮一峰老师的这篇文章挺不错:http://www.ruanyifeng.com/blog/2014/10/event-loop.html
一、关于MacroTask(宏观任务)和MicroTask(微观任务)
macroTask和microTask是两种任务队列。
大家更熟悉的关于事件循环的机制说法大概是:主进程执行完了之后,每次从任务队列里取一个任务执行。
1、JavaScript引擎对这两种队列有不同的分类:
-
macroTask:
- setTimeout,
- setInterval,
- setImmediate,
- requestAnimationFrame,
- I/O,
- UI rendering
-
microTask:
- process.nextTick,
- Promise,
- Object.observe,
- MutationObserver
我们所熟悉的定时器就属于macroTask。
microTask 在 macroTask 之前执行
2、根据上图,Node.js的运行机制如下。
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。
process.nextTick
方法可以在___当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前___
----触发回调函数。
也就是说,它指定的任务总是发生在所有异步任务之前
。
setImmediate
方法则是在___当前"任务队列"的尾部___
添加事件,也就是说,它指定的任务总是在下一次Event Loop,异步任务之时
执行,这与setTimeout(fn, 0)很像。
看代码:
process.nextTick(function A() {
console.log(1);
process.nextTick(function B(){console.log(2);});
});
setTimeout(function timeout() {
console.log('TIMEOUT FIRED');
}, 0)
// 1
// 2
// TIMEOUT FIRED
我们以setTimeout、process.nextTick、promise为例直观感受下两种任务队列的运行方式。
console.log('main1'); // 主进程-->输出顺序:1
process.nextTick(function() {
console.log('process.nextTick1'); // microTask-->输出顺序:4
});
setTimeout(function() {
console.log('setTimeout'); // macroTask-->输出顺序:6
process.nextTick(function() {
console.log('process.nextTick2'); // macroTask 执行后定义的另一个新的 microTask-->输出顺序:7
});
}, 0);
new Promise(function(resolve, reject) {
console.log('promise'); // 主进程-->输出顺序:2
resolve();
}).then(function() {
console.log('promise then'); // microTask-->输出顺序:5
});
console.log('main2'); // 主进程-->输出顺序:3
别着急看答案,先以上面的理论自己想想,运行结果会是啥?
最终结果是这样的:
main1
promise
main2
process.nextTick1
promise then
setTimeout
process.nextTick2
process.nextTick和 promise then在 setTimeout前面输出,已经证明了macroTask和microTask的执行顺序。
在第一个循环里,process.nextTick1
和Promise.then
这两个microTask是在setTimeout
这个macroTask里之前输出的,这是为什么呢?
因为主进程的代码也属于macroTask(理论支撑:【翻译】Promises/A+规范)。
主进程这个macroTask(也就是main1
、promise
和main2
)执行完了,自然会去执行process.nextTick1
和Promise.then
这两个microTask。
这是第一个循环。
之后的setTimeout
和process.nextTick2
属于第二个循环。
重点说下UI rendering。在HTML规范:event-loop-processing-model里叙述了一次事件循环的处理过程,在处理了macroTask和microTask之后,会进行一次Update the rendering,其中细节比较多,总的来说会进行一次UI的重新渲染。