JavaScript是单线程的,同一时间只能做一件事情,也就是只有一个调用栈(Call Stack),调用栈采用的是后进先出的规则,一次只调用一个任务,可以嵌套。在执行调用栈的时候会先执行同步任务,当遇到异步任务的时候,异步任务会在异步任务有结果后,将注册的回调函数放入异步任务队列(先进先出)。异步任务队列分为宏任务队列和微任务队列。这里就有两个新名词:宏任务和微任务。
异步任务被分为两类:宏任务和微任务。
个人理解:“同步任务、异步任务”与“宏任务、微任务”是描述任务两个不同维度的东西,没有谁属于谁。根据下面的定义会发现宏任务中也包含同步任务(新程序或子程序直接被执行) ?
宏任务队列包括:新程序或子程序直接被执行、事件的回调函数(点击事件触发的回调函数)、setTimeout、setInterval、I/O、UI Rendering等等。宏任务中的事件放在callback queue中,由事件触发线程维护。
微任务队列包括:Process.nextTick(Nodejs独有)、Promise.then.catch.finally、MutationObserver等等。微任务的事件放在微任务队列中,由js引擎线程维护。
那么同步任务、宏任务、微任务是按什么顺序运行的,就要通过事件循环(Event Loop)来解决了。
事件循环的处理步骤
第一步:判断任务是同步任务还是异步任务;如果是同步任务,则同步任务会被加入到调用栈,同步任务会在调用栈中按照顺序等待主线程依次执行;如果是异步任务,进行第二步
第二步: 异步任务会在异步任务有了结果后,将注册的回调函数分别按前面的分类放入到宏任务队列和微任务队列中,当主线程空闲的时候(调用栈被清空),微任务队列中的任务会按照先入先出的原则被读取到栈内等待主线程的执行。
第三步:微任务队列被清空以后,会渲染页面,渲染完页面以后,再按照先进先出的原则去执行宏任务队列中的任务,执行完一个宏任务后再重复第一步直至所有宏任务也被清空;
Demo
console.log('开始');
Promise.resolve().then(function () {
console.log('微任务1');
Promise.resolve().then(() => {
console.log('微任务2');
Promise.resolve().then(() => {
console.log('微任务3');
})
})
})
setTimeout(function () {
console.log('2秒延时的宏任务' + new Date());
Promise.resolve().then(() => {
console.log('微任务嵌套在宏任务内1');
})
}, 2000);
Promise.resolve().then(() => {
console.log('微任务4');
Promise.resolve().then(() => {
console.log('微任务5');
Promise.resolve().then(() => {
console.log('微任务6');
})
})
})
setTimeout(function () {
console.log('宏任务1' + new Date());
Promise.resolve().then(() => {
console.log('微任务嵌套在宏任务内1');
})
}, 0);
setTimeout(function () {
console.log('宏任务2' + new Date());
Promise.resolve().then(() => {
console.log('微任务嵌套在宏任务内2');
})
}, 0);
console.log('结束');
结果为:
?
|