首先JS是单线程的,JS中的代码都是串行的, 前面没有执行完毕后面不能执行
程序运行会从上至下依次执行所有的同步代码
在执行的过程中如果遇到异步代码会将异步代码放到事件循环中
当所有同步代码都执行完毕后, JS会不断检测 事件循环中的异步代码是否满足条件
一旦满足条件就执行满足条件的异步代码
在JS的异步代码中又区分"宏任务(MacroTask)"和"微任务(MicroTask)"
宏任务: 宏/大的意思, 可以理解为比较费时比较慢的任务
微任务: 微/小的意思, 可以理解为相对没那么费时没那么慢的任务
宏任务包括:script(整体代码)
, setTimeout
, setInterval
, requestAnimationFrame
, I/O
,setImmediate
。
其中setImmediate
只存在于Node中,requestAnimationFrame
只存在于浏览器中。
微任务包括: Promise
, Object.observe
(已废弃), MutationObserver
(html5新特性),process.nextTick
。
其中process.nextTick
只存在于Node中,MutationObserver
只存在于浏览器中。
注意点: 所有的宏任务和微任务都会放到自己的执行队列中, 也就是有一个宏任务队列和一个微任务队列
所有放到队列中的任务都采用"先进先出原则", 也就是多个任务同时满足条件, 那么会先执行先放进去的
完整执行顺序
1.从上至下执行所有同步代码
2.在执行过程中遇到宏任务就放到宏任务队列中,遇到微任务就放到微任务队列中
3.当所有同步代码执行完毕之后, 就执行微任务队列中满足需求所有回调
4.当微任务队列所有满足需求回调执行完毕之后, 就执行宏任务队列中满足需求所有回调
... ...
注意点:
每执行完一个宏任务都会立刻检查微任务队列有没有被清空, 如果没有就立刻清空
UI Rendering
不属于宏任务,也不属于微任务,它是一个与微任务平行的一个操作步骤。 HTML规范文档
举个🌰
setTimeout(function() {
console.log('a')
});
new Promise(function(resolve) {
console.log('b');
for(var i =0; i <10000; i++) {
i ==99 && resolve();
}
}).then(function() {
console.log('c')
});
console.log('d');
// b
// d
// c
// a
1.首先执行script下的宏任务,遇到setTimeout,将其放入宏任务的队列里。
2.遇到Promise,new Promise直接执行,打印b。
3.遇到then方法,是微任务,将其放到微任务的队列里。
4.遇到console.log('d'),直接打印。
5.本轮宏任务执行完毕,查看微任务,发现then方法里的函数,打印c。
6.本轮event loop全部完成。
7.下一轮循环,先执行宏任务,发现宏任务队列中有一个setTimeout,打印a。
综上所述,不要说a是最持久的,如果你认为你彻底明白了,给你出道题,看看下面的代码中,谁最持久?
console.log('a');
setTimeout(function() {
console.log('b');
process.nextTick(function() {
console.log('c');
})
new Promise(function(resolve) {
console.log('d');
resolve();
}).then(function() {
console.log('e')
})
})
process.nextTick(function() {
console.log('f');
})
new Promise(function(resolve) {
console.log('g');
resolve();
}).then(function() {
console.log('h')
})
setTimeout(function() {
console.log('i');
process.nextTick(function() {
console.log('j');
})
new Promise(function(resolve) {
console.log('k');
resolve();
}).then(function() {
console.log('l')
})
})
第一轮事件循环:
1.第一个宏任务(整体script)进入主线程,console.log('a'),打印a。
2.遇到setTimeout,其回调函数进入宏任务队列,暂定义为setTimeout1。
3.遇到process.nextTick(),其回调函数被分发到微任务队列,暂定义为process1。
4.遇到Promise,new Promise直接执行,打印g。then进入微任务队列,暂定义为then1。
5.遇到setTimeout,其回调函数进入宏任务队列,暂定义为setTimeout2。
此时我们看一下两个任务队列中的情况
宏任务队列 setTimeout1、setTimeout2
微任务队列 process1、then1
第一轮宏任务执行完毕,打印出a和g。
查找微任务队列中有process1和then1。全部执行,打印f和h。
第一轮事件循环完毕,打印出a、g、f和h。
第二轮事件循环:
1.从setTimeout1宏任务开始,首先是console.lob('b'),打印b。
2.遇到process.nextTick(),进入微任务队列,暂定义为process2。
3.new Promise直接执行,输出d,then进入微任务队列,暂定义为then2。
此时两个任务队列中
宏任务队列 setTimeout2
微任务队列 process2、 then2
第二轮宏任务执行完毕,打印出b和d。
查找微任务队列中有process2和then2。全部执行,打印c和e。
第二轮事件循环完毕,打印出b、d、c和e。
第三轮事件循环
1.执行setTimeout2,遇到console.log('i'),打印i。
2.遇到process.nextTick(),进入微任务队列,暂定义为process3。
3.new Promise直接执行,打印k。
4.then进入微任务队列,暂定义为then3。
此时两个任务队列中
宏任务队列:空
微任务队列:process3、then3
第三轮宏任务执行完毕,打印出i和k。
查找微任务队列中有process3和then3。全部执行,打印j和l。
第三轮事件循环完毕,打印出i、k、j和l。
到此为止,三轮事件循环完毕,最终输出结果为:
a、g、f、h、b、d、c、e、i、k、j、l
以上代码仅在浏览器环境中执行顺序如下,node环境下可能存在不同。