- 执行栈&事件队列
- 宏任务&微任务
- 运行机制
- Node和浏览器事件循环的区别
执行栈&事件队列:
由于JS是单线程,所以后一个任务需要等前面执行完后才可以执行,即所有的任务都要排队。在JavaScript中,无非就是就两种任务:
- 同步任务:进入主线程的执行栈顺序执行。
- 异步任务:进入事件队列等待执行。
宏任务&微任务:
为什么要区分宏任务和微任务?
其实就是规定异步任务的优先级。
- 宏任务:
setInterval
、setTimeout
、script
整体代码、IO - 微任务:
Promise.then()
事件循环运行机制:
- 执行宏任务,在这过程中遇到微任务加入到微任务的任务队列中。
- 执行当前微任务队列中的所有微任务。
- 继续从事件队列中接管下个宏任务。
这种循环往复的方式就是事件循环。
实例:
点击查看代码setTimeout(function() {
console.log('setTimeout');
})
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
执行顺序:
- 先执行宏任务(整个代码),将
setTimeout
注册到宏任务;遇到呢我new Promise
立即执行宏任务打印promise
;继续执行宏任务打印console
。 - 宏任务执行完毕,开始执行微任务,当前微任务队列没有微任务,从消息队列中接管下个宏任务。
- 执行
setTimeout
宏任务,打印setTimeout
所以执行顺序是:promise----->console---->setTimeout
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function () {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function () {
console.log('promise2')
})
}, 0)
执行顺序:
- 先执行宏任务(整个代码),先将第一个
setTimeout
注册到宏任务队列,再将第二个setTimeout
注册到宏任务队列,当前宏任务执行完毕。 - 开始执行当前微任务,当前队列没有微任务,继续接管下一个宏任务。
- 开始执行当前宏任务,执行第一个
setTimeout
,打印timer1
,将Promise
注册到微任务队列,当前宏任务执行完毕。 - 开始执行当前微任务,执行
Promise
,打印promise1
,当前微任务执行完毕。 - 开始接管下一个宏任务,执行第二个
setTimeout
,打印timer2
,将Promise
注册到微任务队列,当前宏任务执行完毕。 - 开始执行当前微任务,执行
Promise
,打印promise2
,当前微任务执行完毕。
所以执行顺序是:timer1---->promise1----->timer2----->promise2
Node.js中的事件循环:
在Node.js的V10以下的事件循环是由libuv实现,V11以上和浏览器相同。
在Node10之前的执行顺序:
- 执行完一个阶段的所有内容。
- 执行完nextTick队列里面的内容(
process.nextTick
独立于事件循环,优先执行) - 然后执行完微任务队列的内容
例如:
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function () {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function () {
console.log('promise2')
})
}, 0)
- 首先执行宏任务,将两个
timer
放入timer
队列。 - 进入
times
阶段,执行第一个打印timer1
,并将promise1
放入微任务队列。 - 执行
timer2
,将promise2
放入微任务队列。 - 执行微任务队列,依次打印
promise1
、promise2
所以执行顺序是:timer1---->timer2---->promise1---->promise2