javascript是一门单线程的非阻塞的脚本语言。单线程意味着javascript在执行代码的任何时候,都只有一个主线程来处理所有的任务,而他的实现方法就是事件循环
其中任务队列又分为两种,宏任务(macro-task)和微任务(micro-task)在最新标准中,它们被分别称为task与jobs,他们的执行顺序是,微任务队列优先于>>>宏任务队列,宏任务也称为分发器,他们的作用就是将任务分发到相应的队列中
macro-task大概包括:
script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括:
process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
其中部分API比如process.nextTick,setImmediate只存在于node.js中,浏览器中并不存在
接下来直接分析例子
console.log('golb1');
setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')
})
})
setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')
})
})
process.nextTick(function() {
console.log('glob1_nextTick');
})
new Promise(function(resolve) {
console.log('glob1_promise');
resolve();
}).then(function() {
console.log('glob1_then')
})
setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')
})
})
process.nextTick(function() {
console.log('glob2_nextTick');
})
new Promise(function(resolve) {
console.log('glob2_promise');
resolve();
}).then(function() {
console.log('glob2_then')
})
setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')
})
})
第一步:宏任务script首先执行。全局入栈。glob1输出。
第二步,执行过程遇到setTimeout。setTimeout作为任务分发器,将任务分发到对应的宏任务队列中。注意setTimeout是直接进入函数调用栈并执行的,他的第一个参数也就是延迟执行函数,才会被分发到setTimeout队列中
setTimeout(function() {
console.log('timeout1');
process.nextTick(function() {
console.log('timeout1_nextTick');
})
new Promise(function(resolve) {
console.log('timeout1_promise');
resolve();
}).then(function() {
console.log('timeout1_then')
})
})
第三步:执行过程遇到setImmediate。setImmediate也是一个宏任务分发器,将任务分发到对应的任务队列中。setImmediate的任务队列会在setTimeout队列的后面执行。
setImmediate(function() {
console.log('immediate1');
process.nextTick(function() {
console.log('immediate1_nextTick');
})
new Promise(function(resolve) {
console.log('immediate1_promise');
resolve();
}).then(function() {
console.log('immediate1_then')
})
})
第四步:执行遇到nextTick,process.nextTick是一个微任务分发器,它会将任务分发到对应的微任务队列中去。
process.nextTick(function() {
console.log('glob1_nextTick');
})
第五步:执行遇到Promise。Promise的then方法会将任务分发到对应的微任务队列中,但是它构造函数中的方法会直接执行。因此,glob1_promise会第二个输出。如果这时候,Promise的构造选项中有一个setTimeout,那么他会被立即执行,但是他的参数1也就是延迟执行函数会被压入setTimeout队列
new Promise(function(resolve) {
console.log('glob1_promise');
resolve();
}).then(function() {
console.log('glob1_then')
})
第六步:执行遇到第二个setTimeout。他将会排在第一个setTimeout的后面
setTimeout(function() {
console.log('timeout2');
process.nextTick(function() {
console.log('timeout2_nextTick');
})
new Promise(function(resolve) {
console.log('timeout2_promise');
resolve();
}).then(function() {
console.log('timeout2_then')
})
})
第七步:先后遇到nextTick与Promise,process.nextTick将glob2_nextTick压入相应队列,然后new Promise将console.log('glob2_promise')进入函数调用栈,并执行出栈,promise.then压入Promise微任务队列
process.nextTick(function() {
console.log('glob2_nextTick');
})
new Promise(function(resolve) {
console.log('glob2_promise');
resolve();
}).then(function() {
console.log('glob2_then')
})
第八步:再次遇到setImmediate
setImmediate(function() {
console.log('immediate2');
process.nextTick(function() {
console.log('immediate2_nextTick');
})
new Promise(function(resolve) {
console.log('immediate2_promise');
resolve();
}).then(function() {
console.log('immediate2_then')
})
})
其中,nextTick队列会比Promie先执行。nextTick中的可执行任务执行完毕之后,才会开始执行Promise队列中的任务。
当当前的微任务队列为空时,称为一个事件循环结束,这点非常重要
这时候开始第二轮循环,从setTimeout中出列一个宏任务,他会重复上述过程,将其中的任务分发到相应的队列中,如果他的setTimeout中嵌套了一个setTimeout,那么他会被压入setTimeout队列底部,在之后的循环中才会执行。
async和await
async和await在任务队列中的执行顺序
async用于定义一个异步函数,他的异步性其实是由他的await所决定的,我们可以把await会将其后面的表达式(可以是promise对象或者常量)返回成一个promise对象
而await xxx下面的语句就相当于xxx.then(resolve)
- 如果xxx为一个promise,且返回值为一个thenable的对象即resove(),reject(),并且此时awati返回的这个promise对象的状态就由它决定,如果返回reslove即fulfilled,那么xxx.then(resolve)就会被执行,反之awati之后的语句就不会执行
- 如果xxx的返回值为一个null或者一个常量,又比如console这种,await会将它用promise.resolve(value)处理成一个状态为fulfilled的promise对象,并将这个值传给await xxx之后的语句也就是xxx.then(resolve(value))中
再来看一个例子
async function async1() {
console.log( 'async1 start' )
await async2()
console.log( 'async1 end' )
}
async function async2() {
console.log( 'async2' )
}
async1()
console.log( 'script start' )
//输出结果
async1 start
async2
script start
async1 end
-
首先async函数在调用时与同步函数无异,直接打印async1 start
-
然后遇到了await async2() 改写一下
Promise.resolve(asyncs()).then(console.log('async1 end')
这时候async2会被直接调用,因为他相当于new promise中的构造选项,他的返回值是async2,被await封装成状态为fulfilled的promise,且他的值被传到then中,then被压入promise.then微队列
-
执行script start
-
执行promise.then队列中的async1 end
总结几点:
- 微任务队列优先于宏任务队列
- process.nextTick队列在微任务队列中优先级最高
- promise的构造选项直接执行
- promise.then才会被压入Promise队列
- setTimeout直接执行,他的延迟函数才会被压入setTimeout队列
- setinterval和setTimeout属于同一个队列
- 当所有微任务队列为空时,称为一个事件循环结束