一些概念
1.同步任务和异步任务的由来
JS的一大特点是单线程,也就是说同一时间只能做一件事,也就意味着所有任务需要排队进行。当有一段代码需要执行很久,就会影响后面代码的执行,就会导致页面卡顿,用户体验差。为了避免这个问题,就出现了异步编程,通过回调函数来存放并执行异步代码,任务也就划分为:同步任务和异步任务。
2. 同步任务,异步任务的解释
-
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
-
异步任务:不进入主线程,而是进入任务队列的任务,只有等主线程任务全部执行完毕,任务队列的任务才会进入主线程执行。异步任务又分为宏任务和微任务。
3. 宏任务和微任务
-
宏任务:script(整体代码)、setTimeout、setInterval、setImmediate(Node.js 环境)、UI事件、I/O(Node.js)
-
微任务:promise中的.then和.catch,process.nextTick()
(Promise并不是完全的同步,在Promise中是同步任务,执行resolve或者reject回调的时候,此时是异步操作,会先将then/catch等放到异步任务中的微任务队列)
4. 执行过程
-
先执行所有同步任务,碰到异步任务放到任务队列中
-
同步任务执行完毕,开始执行当前所有的异步任务
-
先执行任务队列里面所有的微任务
-
然后执行一个宏任务
-
然后再执行所有的微任务
-
再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。
3-6的这个循环称为事件循环Event Loop
事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制
实例理解
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
async function fn() {
await fn2();
console.log(13);
}
function fn2() {
console.log(14);
}
fn()
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
这是一道经典的面试题,现在一步一步来分析。
-
执行主线程的同步代码,打印第一行的1,第2-13行是一个宏任务,放到宏任务队列,14-16是一个微任务,放入微任务队列,17-23声明了两个函数;
-
执行主线程的同步代码,第24行是一个函数调用,进入fn内部,fn其实是声明了一个promise,promise是同步代码,会顺序执行打印14,只有.then里面的代码会加入微任务队列里,这里相当于执行了fn2()之后,再将第19行代码加入一个微任务队列中;
-
回到主线程,碰到一个Promise,Promise是同步任务,.then后面的回调会加入微任务队列,所以会打印26行的7;
-
回到主线程,碰到一个Timeout,Timeout是宏任务,放到宏任务队列;
-
主线程执行完成,开始执行微任务队列内的任务,遵循先进先出的原则,因此依次打印16行的6,19行的13,29行的8;
-
微任务执行结束,开始执行宏任务,先执行第一个宏任务第2-13行,宏任务里面也是遵循先同步后异步,先微任务后宏任务,因此依次打印第3行的2,第8行的4,第5行的3,第11行的5;
-
第一个宏任务执行结束,接着执行第二个宏任务,同上,因此依次打印第32行的9,第37行的11,第34行的10,第40行的12
所以最终执行顺序为:
1
14
7
6
13
8
2
4
3
5
9
11
10
12
执行顺序的总结
-
先执行主线程上的同步任务,将异步任务放入队列,宏任务放入下一个宏任务队列,微任务放入微任务队列,当主线程任务执行完成之后,就按照先进先出的原则执行所有的微任务,微任务执行完成后,开始执行下一个宏任务队列
-
当一个宏任务执行完成,如果微任务队列有微任务,会执行完所有微任务,再执行下一个宏任务
-
promise属于同步任务,但是promise.then()和.catch()属于微任务
-
简单记法:主线程同步任务 = promise > promise.then() > setTimeout