阅读 178

事件循环 Event Loop

事件循环 Event Loop

概念: 因为JavaScript是单线程,也就是同一时间只能执行一个任务,所以所有任务都要排队执行,如果前一个任务执行时间过长,后面的任务就必须等待它执行完毕。

任务分类: 同步任务(synchronous)和异步任务(asynchronous)

同步任务(synchronous)

概念: 主线程上排队执行的任务,只有前一个任务执行完毕,才可以执行后一个任务

异步任务(asynchronous)

概念: 不进入主线程,而进入任务队列的任务,只有当任务队列通知主线程某个异步任务可以执行了,该任务才可以进入主线程执行。

主线程运行流程

  1. 所有同步任务都在主线程上执行,形成一个执行上下文栈(Execution Context Stack)。
  2. 当主线程上的所有任务都执行完毕后,才会从任务队列中获取任务到主线程上来执行。
  3. 不断重复执行上一步。

任务队列

分类: 任务队列中的任务分为宏任务(Macrotask)和微任务(Microtask)。

任务队列运行流程

流程: 一次事件循环只执行宏任务队列队首的任务,这个宏任务执行完毕后再执行新微任务队列中的所有任务。

宏任务(Macrotask)

常见的宏任务:script(整体代码)、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel

微任务(Microtask)

‌ 常见的微任务:Promise.then, Promise.catch,async函数中第一个await之后的所有代码(不包括第一个)

总结所有流程

  1. 先按代码正常顺序从上往下执行主线程
  • 遇到 async 函数,如果只是声明而不是用()调用,就不执行,如果是调用就先执行 async 函数,如果里面有 await 函数,就直接执行到第一个 await 函数结束,执行完后这个函数,把后面的所有包括其他的await函数放到微任务队列中。

  • 遇到 Promise 函数,不论是直接声明还是赋值给一个新变量都直接执行里面的代码,停在执行到 resolve() 或是 reject() 之前,也就是 thencatch 之前,把后面的所有代码放到微任务队列中。

  • 遇到 setTimeout() 不执行直接放到任务队列的宏任务中。

  1. 主线程执行完后,先到任务队列的微任务中查找是否有任务,如果有就执行里面全部的微任务,如果微任务又有宏任务和微任务,就再把其中的微任务执行完毕,把其中的宏任务放到宏任务队列中。

  2. 微任务队列清空后,到任务队列的宏任务中查找是否有任务,如果有就执行最开始添加的第一个宏任务,在执行宏任务时,如果有微任务出现就把这些微任务添加到新的微任务队列中。

  3. 第一个宏任务执行完毕后,就再去微任务队列中执行所有微任务。

  4. 步骤循环2-4直到任务队列全部清空。

代码测试

async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
}

async function async2() {
    console.log("async2");
}

console.log("script start");

setTimeout(function() {
    console.log("setTimeout")
}, 0);

async1();

new Promise(function(resolve) {
    console.log("promise1");
    resolve()
}).then(function() {
    console.log("promise2")
})

console.log("script end");

/*
运行顺序:
"script start"
"async1 start"
"async2"
"promise1"
"script end"
"async1 end"
"promise2"
"setTimeout"
*/

复制代码
文章分类
前端
文章标签