JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。所以为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:
①同步任务(synchronous)
又叫做非耗时任务,指的是在主线程上排队执行的那些任务
只有前一个任务执行完毕,才能执行后一个任务
②异步任务(asynchronous)
又叫做耗时任务,异步任务由JavaScript 委托给宿主环境进行执行
当异步任务执行完成后,会通知JavaScript 主线程执行异步任务的回调函数
同步任务和异步任务的执行过程:
①同步任务由JavaScript 主线程次序执行
②异步任务委托给宿主环境执行
③已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
④JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
⑤JavaScript 主线程不断重复上面的第4 步
JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为EventLoop(事件循环)。
异步任务分为宏任务和微任务
微任务:
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then
- MutaionObserver
- Object.observe(已废弃;Proxy 对象替代)
- process.nextTick(Node.js)
宏任务:
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- script (可以理解为外层同步代码)
- setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
按照这个流程,它的执行机制是:
执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完
题目1:
console.log(1)
setTimeout(()=>{
console.log(2)
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise')
resolve()
}).then(()=>{
console.log('then')
})
console.log(3)
输出是 1=>‘new Promise’=>3=>then=>2
分析:
- console.log(1) ,同步任务,主线程中执行
- setTimeout() ,异步任务(遇到定时器,属于新的宏任务,留着后面执行
- new Promise ,同步任务,主线程直接执行)
- .then ,异步任务(属于微任务,放入微任务队列,后面再执行) console.log(3),同步任务,主线程执行
- 本轮宏任务执行完毕,现在去微任务列表查看是否有微任务,发现 .then 的回调,执行它,打印 ‘then’
- 当一次宏任务执行完,再去执行新的宏任务,这里就剩一个定时器的宏任务了,执行它,打印 2
题目2
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
输出: script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout
分析:
- 执行整段代码,遇到 console.log(‘script start’) 直接打印结果,输出 script start
- 遇到定时器了,它是宏任务,先放着不执行
- 遇到 async1(),执行 async1 函数,先打印 async1 start,下面遇到await怎么办?先执行 async2,打印
async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码 - 跳到 new Promise 这里,直接执行,打印 promise1,下面遇到 .then(),它是微任务,放到微任务列表等待执行
- 最后一行直接打印 script end,现在同步代码执行完了,开始执行微任务,即 await 下面的代码,打印 async1 end
- 继续执行下一个微任务,即执行 then 的回调,打印 promise2
- 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 settimeout
题目3
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');
})
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 7 6 8 2 4 3 5 9 11 10 12
- 先执行1 输出1
- 执行到2,把setTimeout放入异步的任务队列中(宏任务) 执行到
- 把process.nextTick放入异步任务队列中(微任务)
- 执行到4,上面提到promise里面是同步任务,所以输出7,再将then放入异步任务队列中(微任务)
- 执行到5,同2
- 上面的同步任务全部完成,开始进行异步任务
- 先执行微任务,发现里面有两个微任务,分别是3,4压入的,所以输出6 8
- 再执行一个宏任务,也就是第一个setTimeout
- 先输出2,把process.nextTick放入微任务中,再如上promise先输出4,再将then放入微任务中
- 再执行所以微任务输出输出3 5
- 同样的,再执行一个宏任务setTImeout2,输出9 11 在执行微任务输出10 12