jascript事件循环机制

13次阅读

共计 2183 个字符,预计需要花费 6 分钟才能阅读完成。

      事件循环机制控制了 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。

正文完
 0