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
事件循环就是任务在主线程不断进栈出栈执行的一个循环过程。
- 优先执行同步队列中的同步任务
- 执行完同步任务后,执行微任务
- 执行完微任务后,执行宏任务
注意:
- 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() 返回之前,所有监听该事件的事件处理程序将在代码继续前执行并返回。 参考
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中,代码执行的顺序是:
- 默认同步代码按照顺序自上而下,从左到右执行,运行过程中注册本次的微任务和后续的宏任务
- 对于微任务,直接放入任务队列,在下一次宏任务开始前立即执行
- 对于宏任务,放入工作线程,等宏任务获得结果后进入任务队列
- 执行本次同步代码中注册的微任务, 并注册微任务中包含的宏任务和微任务
- 将下一次宏任务开始前的微任务执行完毕
- 执行最先进入任务队列的宏任务,并注册此次宏任务中的 微任务和后续的宏任务,同样的微任务放入任务队列,在下一次宏任务开始前执行, 宏任务放入工作线程,等获得结果后进入任务队列