Javascript是单线程执行的,出现异步时,并不会阻塞JS的执行,而是接着往下执行,等到异步结果返回时再处理。对于这些异步的执行任务,就依靠JS的事件循环机制,即EventLoop。
JS中的任务,可以分为宏任务(macrotask)和微任务(microtask)。这两种任务分别由两个队列来维护,这两个队列都遵循先进先出的机制。
- 宏任务:整体代码main script,setTimeout,setInterval,I/O,UI交互事件,postMessage,MessageChannel,setImmediate(Node.js环境)
- 微任务:Promise.then(),MutationObserver,process.nextTick(Node.js 环境)
具体的执行过程:
- 从宏任务队列中取出第一个任务开始执行(我的理解是整体代码是宏任务队列中的第一个任务)
- 执行过程中遇到微任务就把微任务添加到微任务队列,遇到宏任务就把宏任务添加到宏任务队列
- 宏任务执行完后,查看微任务队列,如果有任务,就按顺序执行微任务,知道执行完毕
- GUI 渲染;
- 从宏任务队列中取出下一个宏任务开始执行,回到第2步
让我们来看一道题,分析在注释中:
function promise1(){
return new Promise((resolve)=>{
console.log('async1 start') //4.promise立即执行,输出async1 start
promise2().then(()=>{ //6.回调函数添加到微任务,微1
console.log('async1 end') //13.微1执行,输出async1 end
resolve()
})
})
}
function promise2() {
return new Promise((resolve)=>{
console.log('async2') //5.promise立即执行,输出async2
resolve()
})
}
console.log("script start"); //1.整体代码宏任务执行,输出script start
setTimeout(function () { //2.回调函数添加到宏任务队列,宏1
console.log("settimeout"); //15.宏1执行,输出settimeout
});
promise1() //3.执行函数promise1
new Promise(function (resolve) {
console.log("promise1"); //7.promise立即执行,输出promise1
resolve();
}).then(function () { //8.回调函数加入微任务队列,微2
console.log("promise2"); //14.微2执行,输出promise2
});
setImmediate(()=>{ //9.回调函数加入宏任务队列,宏2
console.log("setImmediate") //16.宏2执行,输出setImmediate
})
process.nextTick(()=>{ //10.回调函数加入微任务队列,微3
console.log("process") //12.process.nextTick优先级高于promise,微3执行,输出process
})
console.log('script end'); //11.整体代码执行,输出script end
过程解析:
- 宏任务队列中只有一个宏任务,就是整体代码,并开始执行,输出script start
- 遇到setTimeout,将回调函数添加到宏任务队列,即宏任务队列为[console.log("settimeout"),],继续向下执行
- 遇到promise1函数,promise1入栈
- promise1中,new Promise(fn)中的fn会立即执行,输出async1 start,继续执行
- 遇到promise2函数,promise2入栈
- 执行promise2函数体,new Promise(fn)中函数立即执行,输出async2,返回一个promise,promise2出栈,继续执行promise1
- promise2().then是微任务,回调函数加入微任务队列,即当前微任务队列[console.log('async1 end')],promise1出栈,继续执行主体代码
- new Promise(fn)中fn立即执行,输出promise1,then方法中的函数加入微任务队列,则当前微任务队列为[console.log('async1 end'),console.log("promise2")],继续向下执行
- setImmediate是宏任务,回调函数加入宏任务队列,即当前宏任务队列为[console.log("settimeout"),console.log("setImmediate")],继续向下执行
- process.nextTick是微任务,回调函数加入微任务队列,即当前微任务队列为[console.log('async1 end'),console.log("promise2"),console.log("process")],继续向下执行
- 主体代码执行,输出script end
- 当前宏任务执行完毕,查看微任务队列为[console.log('async1 end'),console.log("promise2"),console.log("process")]
- 因为process.nextTick优先级高于promise,所以先执行,输出process,然后执行其他两个微任务,先后输出async1 end,promise2
- 微任务队列清空后,查看宏任务队列,为[console.log("settimeout"),console.log("setImmediate")]
- setTimeout的优先级高于setImmediate,所以先取出setTimeout宏任务并执行,输出settimeout
- 当前宏任务执行完毕,查看微任务队列,没有微任务,则继续执行下一个宏任务,输出setImmediate
所以执行结果是:script start,async1 start,async2,promise1,script end,process,async1 end,promise2,settimeout,setImmediate
优先级:process.nextTick()>Promise.then()>setTimeout>setImmediate。