谈谈事件循环

249 阅读2分钟

单线程、同步和异步

JS是单线程,单线程即任务是串行的,后一个任务需要等待前一个任务的执行,这就可能出现长时间的等待。但由于类似ajax网络请求、setTimeout时间延迟、DOM事件的用户交互等,这些任务并不消耗 CPU,是一种空等,资源浪费,因此出现了异步。通过将任务交给相应的异步模块去处理,主线程的效率大大提升,可以并行的去处理其他的操作。当异步处理完成,主线程空闲时,主线程读取相应的callback,进行后续的操作,最大程度的利用CPU

此时出现了同步执行和异步执行的概念,同步执行是主线程按照顺序,CPU串行执行任务(通过执行调用栈,先进后出);异步执行就是跳过等待,先处理后续的同步任务(不是说异步不执行了,而是交给网络模块、timer等并行进行任务)。因此产生了事件循环机制。

宏任务和微任务

现在说事件循环就必须先知道宏任务和微任务的概念。

  • 宏任务
  1. 新程序或子程序被直接执行,比如script、在控制台直接写代码。
  2. 事件回调函数,比如鼠标点击事件
  3. setTimeoutsetInterval
  4. I/O操作、requestAnimationFrame等。
  • 微任务
  1. Promise,常用的就是这个。
  2. MutationObserver
  3. Node.jsProcess.nextTick()

事件循环

  1. 在执行调用栈的时候会先执行同步任务。
  2. 调用栈在发现异步任务时候会把异步任务放入队列中,异步任务分为宏任务和微任务,队列按照先入先出的规则。
  3. 事件循环就是会不断寻找可以执行的任务,同步任务执行完后也就是调用栈清空后,会去执行微任务队列。
  4. 微任务队列清空后才会去执行宏任务。

示例

 console.log('sync')
 Promise.resolve().then(function promise1 () {
        console.log('promise1');
     })
 setTimeout(function setTimeout1 (){
     console.log('setTimeout1')
     Promise.resolve().then(function  promise2 () {
        console.log('promise2');
     })
 }, 0)
 ​
 setTimeout(function setTimeout2 (){
    console.log('setTimeout2')
 }, 0)
 ​
 ​
 ​
 ​
 // sync
 // promise1
 // setTimeout1
 // promise2
 // setTimeout2

事件循环过程:

  1. 第一轮

    整个script是一个宏任务,在调用栈中执行。

    script推入宏任务。

    sync为同步任务,promise1为微任务,setTimeout1列为宏任务,setTimeout2列为宏任务。

    执行完毕清空调用栈。微任务中有promise1,执行完毕后浏览器可能进行渲染。

    script退出宏任务。

    事件循环开始寻找下一个可以执行的任务。

  2. 第二轮

    取出setTimeout1进入调用栈执行完毕,并把promise2推入微任务。

    promise2执行完毕,浏览器可能进行渲染。

    事件循环开始寻找下一个可以执行的任务。

  3. 第三轮

    取出setTimeout2进入调用栈执行完毕。

参考

tasks-microtasks-queues-and-schedules