动图详解Eventloop面试题,我终于会做了!!!

67 阅读4分钟

宏任务

宏任务在主线程上执行,包括:

  1. 渲染时间(解析DOM、计算布局、放大缩小等)
  2. 用户交互事件(如鼠标点击、滚动页面、放大缩小等)
  3. JavaScript脚本执行时间
  4. 网络请求完成、文件读写完成事件

主线程采用for循环,从任务队列中取出任务并执行任务,队列里的任务就叫宏任务。宏任务是主线程添加到任务队列中的,不能控制它在任务队列的位置也就不能控制任务开始的时间,因此也会产生一些问题。

    function timerCallback2(){
        console.log(2)
    }
    function timerCallback(){
        console.log(1)
        setTimeout(timerCallback2,0)
    }
    setTimeout(timerCallback,0)
    

上面代码设置了两个setTimeout的回调任务,计划是它们两个任务先后次序执行,中间没有其他任务,现实情况是不可控的,两个回调任务中间有可能会插入很多其他任务,影响第二个回调任务的执行。

微任务

当前宏任务执行时,有时候会产生多个微任务,这时候就需要用微任务队列来保存这些微任务了。也就是说每个宏任务都关联了一个微任务队列,接下来分析两个最要的问题,一个是微任务是什么时间产生的,还有一个微任务是什么时间执行的? 首先微任务有两种方式产生:

  1. 第一种方式是使用MutationObserver 监控DOM节点,DOM发生了变化,observer会把变化记录在变化数组中,等待一起都结束了,然后一次性的从变化数组中执行对应的回调函数。当DOM节点变化时,就会产生DOM变化记录的微任务。
  2. 第二种方式是使用Promise,当调用Promise.resolve()或者Promise.reject()的时候,也会产生微任务。 产生微任务之后,JavaScript引擎会把他加入到微任务队列中。 未命名文件.png 未命名文件 (1).png

JavaScript脚本执行的过程中,分别通过Promise和removeChild创建了两个微任务,并添加到微任务列表中。JavaScript脚本执行结束之后,准备退出全局执行上下文时,到达检查点,JavaScript引擎会检查微任务队列,发现微任务队列中有微任务,接下来会依次执行这两个微任务。等微任务队列清空之后退出全局执行上下文。

一道面试题引发的思考

console.log(1)
setTimeout(function(){
    console.log(2)
    new Promise(function(resolve){
        resolve(3)
    }).then(function(res){
        console.log(res)
    })
}, 100)
new Promise(function(resolve){
    console.log(5)
    resolve()
}).then(function(){
    console.log(6)
})
setTimeout(function(){
    console.log(7)
    new Promise(function(resolve){
        console.log(8)
        resolve()
    }).then(function(){
        setTimeout(() => {
            console.log(9)
        }, 100)
    })
})
console.log(10)

首先,当代码执行的时候,整体代码script进入宏任务队列中,并开始执行宏任务。首先执行console.log(1),执行完后移出调用栈。 1.gif 接下来将setTimeout(callback,100)推入调用栈,setTimeout()方法属于事件循环模型中WebAPIs中的方法,引擎在将setTimeout()方法出栈执行时,将延时执行的函数交给了相应模块,即图右方的timer模块来处理。 2.gif 接着将Promise语句推入调用栈,执行console.log,接下来是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。 3 上午10.39.25.gif 接着执行console.log(10),这时script宏任务执行结束,移出宏任务队列。接下来清空微任务队列。首先执行的是Promise then,推入调用栈中。执行console.log(6),then方法执行完成移出调用栈和微任务队列。 4.gif 接下来又是setTimeout方法,同样是将setTimeout方法交给了相应模块。 5-2.gif timer模块检测到setTimeout2达到触发条件,将setTimeout2 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout2 callback推入调用栈,执行console.log(7)。 6.gif 接着是将new Promise推入执行栈,执行console.log(8),接下来是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。 7.gif 这时候宏任务setTimeout2 callback执行结束,移出执行栈,紧接着开始清空微任务队列。接着执行Promise then方法,接着将setTimeout3函数交给timer模块来处理。then方法执行完成,调用栈和微任务队列都被清空。 8-1.gif timer模块检测到setTimeout1达到触发条件,将setTimeout1 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout1 callback推入调用栈,执行console.log(2)。 9.gif 接着是将new Promise推入执行栈,然后是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。setTimeout1也执行完成,移出调用栈。 10-1.gif 宏任务执行结束,紧接着清空微任务队列,执行Promise then方法,执行console.log(res)。 11-1.gif timer模块检测到setTimeout3达到触发条件,将setTimeout3 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout3 callback推入调用栈,执行console.log(9)。 12.gif