《前端随岁月》系列将我平时看书或文章时遇到的问题记录下来,一般都是比较基础但是知识点容易忘记。你也可能在面试中遇到。我会查一些资料,可能还会加上自己的理解来记录这些问题。更多文章请到我的个人博客。这个问题是关于执行顺序和事件循环的。关于EventLoop、任务队列等概念,可以看我先引用的那篇文章。本文主要分析存在的一些疑惑。下面的例子很典型:setImmediate(function(){console.log(1);},0);setTimeout(function(){console.log(2);},0);newPromise(function(resolve){console.log(3);resolve();console.log(4);}).then(function(){console.log(5);});console.log(6);process.nextTick(函数(){console.log(7);});console.log(8);//输出为34687521在解释输出之前,我们先看几个概念:macro-task:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UIrendering.micro-task:process.nextTick,Promise(native),Object.observe,MutationObserver除了脚本整体代码外,微任务的任务优先级为高于宏任务的任务优先级。其中,script(整体代码)可以理解为所有要执行的代码。所以执行顺序如下:Step1.执行脚本整体代码,执行过程为创建setImmediate宏任务,创建setTimeout宏任务,创建微任务Promise.then回调,并执行脚本console.log(3);解决();控制台日志(4);此时输出3和4。虽然调用并执行了resolve,但是整体代码还没有执行完,无法进入Promise.then流程。console.log(6)output6process.nextTickcreatemicro-taskconsole.log(8)output8经过第一个过程,第二步3468已经输出。由于其他微任务比宏任务具有更高的优先级。此时微任务中有两个优先级process.nextTick高于Promise的任务。所以先输出7,再输出5。第三步,微任务任务列表已经执行完了,往下执行宏任务。由于setTimeout的优先级高于setIImmediate,所以先输出2,再输出1。整个过程描述为同步操作,但实际上是基于EventLoop的事件循环。关于微任务和宏任务的执行顺序,可以看下面的例子(来自《深入浅出Node.js》)://添加两个nextTick回调函数process.nextTick(function(){console.log('nextTick延迟执行1');});process.nextTick(function(){console.log('nextTick延迟执行2');});//添加两个setImmediate()回调函数setImmediate(function(){console.log('setImmediate延迟执行1');//进入下一个循环process.nextTick(function(){console.log('强力插入');});});setImmediate(function(){console.log('setImmediate延迟执行2');});console.log('正常执行');书上给出的执行结果是:正常执行nextTick延迟执行1nextTick延迟执行2setImmediate延迟执行1强插入setImmediate延迟执行2process.nextTick在两个setImmediate之间强行插入。但是当我运行这段代码的时候,发现结果是这样的:正常执行nextTick延迟执行1nextTick延迟执行2setImmediate延迟执行1setImmediate延迟执行2强插入朴先生写那本书的时候,node的最新版本是0.10.13,而我的版本是6.x老版本的Node会先执行process.nextTick。当执行process.nextTick队列时,会执行一个setImmediate任务。然后再次回到新的事件循环。所以第一次setImmediate执行完后,队列中只剩下第一次setImmediate和第二次setImmediate中的process.nextTick。所以process.nextTick会先被执行。在新版本的Node中,process.nextTick执行后,会循环setImmediate,执行完所有setImmediate后跳出循环。所以两次setImmediates执行完后,队列中只剩下第一次setImmediate中的process.nextTick。最后输出“强插入”。具体实现请参考Node.js源码。另一个关于优先级更清晰的版本:observerpriority在每次轮换检查中,每个observer的优先级是:idleobserver>I/Oobserver>checkobserver。idleobserver:process.nextTickI/Oobserver:通用I/O回调,如网络,文件,数据库I/O等checkobserver:setImmediate,setTimeout,setImmediateandsetTimeoutpriority看下面的例子:setImmediate(function(){console.log('1');});setTimeout(function(){console.log('2');},0);console.log('3');//输出为321我们知道HTML5现在规定setTimeout的最小间隔时间是4ms,也就是说0实际上会默认设置为最小值4ms。让我们增加这个延迟,并假设setTimeout的优先级高于setImmediate。其实这个说法是有条件的。看下面的例子,在setTimeout中加入20ms的延迟:setImmediate(function(){console.log('1');});setTimeout(function(){console.log('2');},20);console.log('3');//输出结果为321setTimeout延迟20ms才执行,但是setImmediate是立即执行的,所以是先输出2转1??尝试打印出这个程序的执行时间:vart1=+newDate();setImmediate(function(){console.log('1');});setTimeout(function(){console.log('2');},20);console.log('3');vart2=+newDate();console.log('time:'+(t2-t1));//输出3时间:2321程序执行耗时23ms,也就是说setTimeout在脚本(整个代码)执行之前就已经过时了,所以进入宏任务时setTimeout仍然优先于setImmediate执行。如果我们稍微增加这个值会怎样?vart1=+newDate();setImmediate(function(){console.log('1');});setTimeout(function(){console.log('2');},30);console.log('3');vart2=+newDate();console.log('time:'+(t2-t1));//输出3time:2312setImmediate比setTimeout早执行,因为进入了macro-当任务循环时,setTimeout定时器还没有到达。以上实验基于6.6.0版本的Node.js测试。其实遇到此类问题,最好的办法就是参考标准,查看源码。你不能记住概念和顺序,因为标准也会改变。包括这篇文章也是自学总结,供参考。参考:https://www.zhihu.com/questio...https://segmentfault.com/a/11...http://www.jianshu.com/p/837b...
