先记录两个任务队列:宏任务(macro-task) 和 微任务(micro-task)
宏任务:script(整体代码),setTimeout,setInterval,setImmediate,I/O,UI rendering
微任务:process.nextTick, Promise.then等,Object.observe(用于观察对象的变动),MutationObserver(用于监视DOM变动)。
其中:setImmediate和process.nextTick是nodejs里的API,浏览器中并没有
在调用栈中遇到DOM操作、ajax操作以及setTimeout等WEBAPIs的时候会交给浏览器内核其他模块先行处理,如DOM Binding、network和timer分别处理上述三种API。等这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等待栈中任务执行完后去任务队列中调用回调函数。
例:通过setTimeout说明事件循环机制
console.log(1);
setTimeout(function cb(){
console.log('2');
}, 3000);
console.log(3);
执行步骤:
1、首先全局执行上下文入栈,全局上下文会位于调用栈最底部,形成一个全局对象
2、代码接着执行,遇到console.log(1);由于是同步任务,立即执行,此时输出1,执行完毕后console.log(1)出栈
3、代码接着执行,遇见setTimeout,执行引擎先将其添加到调用栈,但调用栈发现其为WEBAPIs中的API,将其出栈,并将延时执行函数交由timer模块处理。此时代码继续执行console.log(3);输出3,执行完毕后出栈
4、timer模块处理延时完成,将cb()放入任务队列,此时调用栈任务已执行完毕
5、由于调用栈任务已完成,会前往任务队列中查看是否有需要执行的回调函数,此时会将cb()添加到调用栈中,并执行里面console.log(3)方法,输出3,等到任务执行完毕后再出栈。
事件循环的顺序是从script开始第一次循环,然后全局上下文进入调用栈,碰到宏任务就将其交给处理它的模块,处理完成后放入宏任务的任务队列中。微任务就放入微任务队列。上一个宏任务执行完成后会检查微任务队列中是否有任务,如果有微任务就执行所有微任务,执行完毕后再去执行下一个宏任务
注意:1、任务队列分为宏任务队列和微任务队列
2、宏任务队列可以有多个,第一个宏任务队列只有一个宏任务为执行主线程中的js代码。微任务队列只能有一个。
1、新程序script执行归为宏任务
2、同步任务执行完成之后会先去执行微任务,再去执行宏任务
3、不同任务会放入不同的任务队列
4、先执行宏任务,再执行微任务队列中的所有微任务,再执行下一条宏任务,如此循环
5、当有多个宏任务和微任务的时候,事件循环顺序是按文章开头列出的宏任务和微任务的顺序执行
流程:
const button = document.querySelector('button');
// 点击1
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('microtask 1')
});
console.log('listener 1');
})
// 点击2
button.addEventListener('click', () => {
Promise.resolve().then(() => {
console.log('microtask 2')
});
console.log('listener 2');
})
// 触发方式1: 手动点击按钮触发
// 触发方式2: button.click();
触发方式1触发结果:
依次打印 listener 1 — microtask 1 — listener 2 — microtask 2
为啥呢?查询资料加上自己的理解:
点击时,js调用栈会添加点击1的宏任务,执行addEventListener同步任务,内部嵌套了console.log()的同步任务,优先执行,执行完毕后执行promise的then微任务,所以打印 listener 1 — microtask 1 。执行完毕后点击1的宏任务出栈,添加点击2的宏任务,再执行点击2内部的任务,打印 listener 2 — microtask 2
触发方式2触发结果:
依次打印listener 1 — listener 2 — microtask 1 — microtask 2
执行顺序:
先向调用栈添加click的宏任务,click中有两个同步任务点击1和点击2,在点击1中promise.then()是微任务,放入微任务队列,打印 listener 1,点击1打印完成后,click的宏任务并没有执行完成,会继续向下执行点击2的同步任务,点击2中promise.then()添加到微任务队列,打印listener 2,此时宏任务中挂载了两个微任务,检查微任务队列,依次打印microtask 1和microtask 2。
也就是说:触发方式1代码依次执行会创建两个点击事件的宏任务,触发方式2是创建一个click事件宏任务。有问题之处恳请指正