事件循环机制控制了javascript代码的执行顺序。我们都知道javascript是单线程,这个线程中拥有唯一的一个事件循环。(新标准web workker有多线程的概念。)而事件循环机制主要以来调用栈来处理执行顺序,依靠任务队列来执行代码的执行。队列的概念可以参考https://segmentfault.com/a/11...
      在一个线程中,调用栈是唯一的,但是任务队列可以是多个,并且分为macro-task(宏任务)及micro-task(微任务)两种类型。
图片描述
      这里需要区分一个概念任务及任务源。setTimeout及Promise是任务源。他们指定具体的执行任务进入任务队列。只有回调中的函数才会进入任务队列。就像setTimeout它其实是丽姬执行的,只是它的回调函数才会延迟执行。promise也是,本身是立即执行的,但是then才会在“未来”执行。
      javascript的执行顺序是从整体代码开始做循环,之后全局上下文进入函数调用栈。直到调用栈清空。整体代码所处的macro-task执行完成,轮到micro-task任务执行。一直循环直到所有的任务执行完成。
      当然,不同的任务源的任务会进入不同的任务队列。
      具体的可以参考一下代码。
图片描述
      1.事件循环从macro-task开始,整体代码开始执行。整体代码script进入macro-task,并且执行代码main进入调用函数调用栈。遇到第12行的打印输出start
图片描述
      2.继续执行,遇到13行的setTimeout,它是宏任务源。便将其分发到对应的队列中。接着遇到16行的promise。promise.resolve会进入函数调用栈直接执行,因此打印promise1,接着将p.then1和p.then分发到对应的微任务队列中。继续执行代码,遇到第24行的打印便输出end。大致图示如下图。
图片描述
      3.script执行完毕,即第一个宏任务执行完毕,开始执行微任务。现在微任务只有一个队列,里面有p1.then1,p1.then2。队列是先进先出,因此先执行p1.then1,p1.then1进入函数调用栈,输出then1。
图片描述
      4. p1.then1执行完毕之后,出栈。但是此时的正在进行的微任务还未执行完完毕,会继续执行p1.then2,p1.then2进入函数调用栈,输出then2。此时,微任务正在进行的队列已经执行完毕。
图片描述
      5.当微任务执行完毕之后,第一轮循环结束,进入第二轮循环,继续执行宏任务,此时setTimeout执行,进入函数调用栈,输出setTimeout1。
图片描述
      6.此时,宏任务队列和微任务队列中都没有任务了。代码执行完毕,就不会有任何输出了。

      我们上述的代码只涉及到一个宏任务及微任务队列的情况。但如果情况更加复杂会有什么样的表现呢?大家可以看看下面的代码。根据上面的原理试着自己分析下结果~
图片描述
图片描述
      1.还是跟以前的例子一样,事件循环从macro-task开始,整体代码开始执行。输出start。setTimeout1,setTimeout2依次进入新的宏任务队列。p3.resolve执行,输出promise31,promise31。并将setTimeout3放入新的宏任务队列。因为setTimeout3不是整体代码中定义的,而是在promise中定义的,需要重新开启一个宏任务队列。然后p3.then1,p3.then2分别进入微任务队列。p3.resolve出栈后,整体代码继续执行,这里就不重新画图了,输出end。
图片描述
      2.整体代码已执行完成,循环进入微任务。此时p3.then1进入函数调用栈。输出then31。遇到新的定时,将set4放入宏任务队列。遇到新的promise,继续将p4.resolve入栈。输出promise41,promise42。遇到新的定时,将set5放入宏任务队列。此时需要注意的是,在微任务中继续有promise。此时的promise.then不再进入微任务队列,而是直接执行。因此输出then41。
图片描述
      3.微任务队列还未执行完毕,继续执行p3.then2。直接输出then32。此时微任务队列已经执行完毕,进入下一轮循环。
图片描述
      4.新的循环开始。队列是先进先出,因此在宏任务当前队列中,set1先执行,进入函数调用栈。输出setTimeout1。遇到新的promise,继续将p1.resolve入栈。输出promise1。还是跟上看一样,在宏任务中继续有promise。此时的promise.then不再进入微任务队列,而是直接执行。直接输出then1。
图片描述
      5.setTimeout1执行完毕,正在执行的宏任务队列还有任务,继续执行setTimeout2。setTimeout2进入函数调用栈。跟setTimeout1的分析一样,陆续输出setTimeout2,promise2,then2。
图片描述
      6.当前宏任务执行完毕,微任务内没有可执行的队列。继续下一轮循环。执行set3。输出setTimeout3。遇到新的promsie,还是跟上面的分析一样,输出promise5,then5。因为微任务一直没有可执行的队列。宏任务内的队列依次执行,输出setTimeout4,setTimeout5。
图片描述


miya
249 声望8 粉丝

前端工程师