JS事件循环

为什么会有event loop

因为js是单线程的,最大的特点就是容易出现阻塞问题。所以设计了event loop来解决这个问题,并规范了同步任务和异步任务两种。

同步任务与异步任务

异步任务又分为:微任务(micro task) 和 宏任务(macro task)

微任务:

  • new Promise(func).then()中的then()的代码
  • process.nextTick(node环境)
  • MutationObserver(浏览器)

宏任务:

  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • DOM事件
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

注意: new Promise(func).then()中的func代码是同步任务,then()是异步代码 执行顺序setImmediate永远优先于setTimeout 除上述异步任务,其他都是同步任务。

任务执行顺序

同步任务->微任务->宏任务

什么是event loop

事件循环就是任务在主线程不断进栈出栈执行的一个循环过程。

  1. 优先执行同步队列中的同步任务
  2. 执行完同步任务后,执行微任务
  3. 执行完微任务后,执行宏任务

流程

注意:

  • setTimeout、setInterval的回调函数并不是直接放到宏任务队列中,当程序遇到setTimeout函数时,会先将setTimeout暂时交给一个js引擎的Timer模块管理,等倒计时完成后Timer模块才将setTimeout的回调函数放到宏任务队列中
  • await后面的代码是异步代码
  • new Promise(func).then()中的func代码是同步任务,then()是异步代码
  • Promise没有返回值时(没有执行resolve或reject),后面的代码不会执行
  • Promise.then().then()链式调用时,上一层then一定要有返回值,否则下一层的then的res就是undefined,finally()的res是then的返回值。

addEventListener和dispatchEvent输出问题(难点)

dispatchEvent和经由浏览器触发,并通过事件循环异步调用事件处理程序的“原生”事件不同,dispatchEvent() 会同步调用事件处理函数。在 dispatchEvent() 返回之前,所有监听该事件的事件处理程序将在代码继续前执行并返回。 参考open in new window

console.log('本轮任务');
new Promise((resolve, reject) => {
    resolve()
}).then(() => {
    console.log('本轮微任务');
})
setTimeout(() => {
    console.log('setTimeout');
},0)
document.getElementById('btn').addEventListener('click', () => { 
    Promise.resolve().then(() => {
        console.log('p1')
    })
    console.log('click1');
})
document.getElementById('btn').addEventListener('click', () => { 
    Promise.resolve().then(() => {
        console.log('p2')
    })
    console.log('click2');
})
document.getElementById('btn').click() //浏览器自动执行的话,这一步变为同步代码,其原理是dispatchEvent
// var event = new Event('click')
// document.getElementById('btn').dispatchEvent(event)
console.log('本轮任务end')
// 上述代码输出:本轮任务、click1、click2、本轮任务end、本轮微任务、p1、p2、setTimeout

//如果是用户点击按钮,则输出:click1、p1、click2、p2

总结

在JavaScript中,代码执行的顺序是:

  • 默认同步代码按照顺序自上而下,从左到右执行,运行过程中注册本次的微任务和后续的宏任务
  • 对于微任务,直接放入任务队列,在下一次宏任务开始前立即执行
  • 对于宏任务,放入工作线程,等宏任务获得结果后进入任务队列
  • 执行本次同步代码中注册的微任务, 并注册微任务中包含的宏任务和微任务
  • 将下一次宏任务开始前的微任务执行完毕
  • 执行最先进入任务队列的宏任务,并注册此次宏任务中的 微任务和后续的宏任务,同样的微任务放入任务队列,在下一次宏任务开始前执行, 宏任务放入工作线程,等获得结果后进入任务队列