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')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
来,也给各位 2 分钟的时间,思考一下,这段代码打印输出的结果应该是什么?
要想理解这段代码,我们得先搞懂几个概念:
浏览器中 Javascript 执行线程
在浏览器中,JavaScript执行线程是单线程的,也就是说在同一时间只能执行一个任务。这是由JavaScript的设计决定的,主要是为了保证页面的稳定性和安全性。
JavaScript执行线程的主要任务是执行JavaScript代码,并处理与之相关的事件。
当浏览器加载一个页面时,会创建一个JavaScript执行线程,并将JavaScript代码按照顺序执行。
在执行过程中,如果遇到需要等待的操作(比如网络请求、定时器等),JavaScript执行线程会将这些操作交给浏览器的其他线程处理,自己则继续执行下面的代码。
需要注意的是,虽然JavaScript执行线程是单线程的,但浏览器是多进程的。
每个标签页通常会有一个独立的渲染进程,每个进程都有自己的JavaScript执行线程。
这样可以避免一个页面的JavaScript代码影响到其他页面的执行,提高了浏览器的稳定性和安全性。
浏览器中常见的其他线程包括:
-
GUI线程:负责渲染页面和处理用户交互事件。当JavaScript执行线程执行时,GUI线程会被挂起,直到JavaScript执行完毕才会继续处理GUI更新。
-
定时器线程:负责管理定时器,当定时器到期时会将相应的回调函数放入JavaScript执行线程的任务队列中,等待执行。
-
事件线程:负责处理DOM事件,当用户触发了一个事件(比如点击按钮),事件线程会将相应的事件回调函数放入JavaScript执行线程的任务队列中,等待执行。
-
异步 http 请求线程:XMLHttpRequest 在连接后浏览器新开线程去请求,检测到状态变化如果有设置回调函数会产生状态变更事件,然后把对应任务添加到处理队列的尾部等到 JS 引擎空闲时处理。
为此,JavaScript 提供了异步操作,比如定时器(setTimeout、setInterval)事件、Ajax请求、I/O回调等。我们可以把高负载的任务使用异步任务进行处理,它们将会被放入浏览器的事件任务队列(event loop)中去,等到JavaScript运行时执行线程空闲时候,事件队列才会按照先进先出的原则被一一执行
JS 的同步任务与异步任务
同步任务和异步任务是指在编程中的两种不同的任务执行方式。
同步任务,是指按照代码的顺序依次执行的任务。
也就是说,前一个任务执行完毕后,才能执行下一个任务。在执行同步任务时,程序会一直等待任务执行完毕,然后再执行下一个任务。
异步任务,是指不按照代码顺序执行的任务。
当遇到异步任务时,程序会继续执行后面的代码,而不会等待异步任务执行完毕。
异步任务会在后台执行,并在任务完成后通知程序进行处理。常见的异步任务包括网络请求、文件读写、定时器等。
事件任务队列
任务队列是用来存储异步任务的队列,任务队列采用先进先出的原则,即先放入队列的任务会先被执行。
任务队列的主要目的是解决任务的异步执行和调度问题。
当有大量任务需要执行时,任务队列可以帮助我们合理地分配任务的执行顺序和资源,提高系统的效率和响应性能。
任务队列通常采用先进先出(FIFO)的原则,即先放入队列的任务会先被执行。当一个任务完成后,下一个任务会从队列中取出并执行。这样可以确保任务按照顺序依次执行,避免任务之间的竞争和冲突。
任务队列中的任务可以是各种类型的,例如计算任务、网络请求、文件处理等。每个任务都包含了执行所需的信息和逻辑。任务队列可以根据需要进行扩展和调整,以适应不同的应用场景和需求。
其中包含了微任务队列和宏任务队列。具体来说,微任务和宏任务的生成和放入任务队列是通过不同的方式实现的。
-
微任务(Microtask):
-
生成方式:微任务主要是由一些内置的异步操作或者一些特定的API产生的,比如Promise的resolve和reject、MutationObserver的回调函数等。
-
放入任务队列:在 JavaScript 的事件循环中,当当前任务执行完毕时,会先检查微任务队列。如果微任务队列不为空,就按照先进先出的顺序依次将微任务放入执行队列中。
-
宏任务(Macrotask):
-
生成方式:宏任务一般是由一些外部事件触发的,比如定时器的回调函数(setTimeout, setInterval)、DOM事件、I/O操作等。
-
放入任务队列:当宏任务生成时,会直接放入任务队列中,等待事件循环时执行。
在JavaScript中,任务队列的执行顺序是有规定的,按照以下顺序执行:
-
执行当前同步任务。
-
执行当前微任务队列中的所有微任务。
-
渲染页面。
-
执行当前宏任务队列中的第一个宏任务。
-
重复步骤 2-4,直到所有的任务都执行完毕。
在事件循环中,每次循环会先执行所有的微任务,然后再执行一个宏任务。这样的循环不断重复,直到任务队列中的所有任务都被执行完毕。
任务队列的机制保证了异步任务的有序执行。通过将任务放入队列中,可以避免阻塞主线程,提高程序的响应性能。
关于微任务和宏任务的应用场景:
微任务和宏任务是任务队列中的两种不同类型的任务。
微任务的应用场景:
-
Promise 回调函数:当 Promise 对象的状态发生变化时,会触发相应的回调函数,这些回调函数会作为微任务放入微任务队列中。
-
MutationObserver:用于监听DOM的变化,当DOM发生变化时,会触发MutationObserver的回调函数,该回调函数会作为微任务放入微任务队列中。
-
process.nextTick:在Node.js环境中,process.nextTick 方法可以将回调函数作为微任务放入微任务队列中。
宏任务的应用场景:
-
定时器:使用 setTimeout 和 setInterval 函数可以创建定时器,它们会将回调函数作为宏任务放入宏任务队列中,在指定的时间间隔后执行。
-
DOM事件:当用户与页面进行交互时,比如点击按钮、滚动页面等,相关的事件回调函数会作为宏任务放入宏任务队列中。
-
I/O操作:包括文件读写、网络请求等异步操作,它们会将回调函数作为宏任务放入宏任务队列中。
需要注意的是,微任务和宏任务的执行顺序不同。
在事件循环中,当当前任务执行完毕时,会先执行微任务队列中的所有微任务,然后再执行宏任务队列中的第一个宏任务。微任务具有较高的优先级,能够更快地得到执行。
现在我们可以来看,这道今日头条的面试题了,不用往回翻,博主贴心的把代码搬到下面来了:
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')
},0)
async1();
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})
console.log('script end')
让我们一步步分析这段代码的执行过程:
-
同步代码执行:首先执行所有的同步代码。
console.log('script start')
这行代码是同步的,所以它是首先输出的。
-
设置 setTimeout:
setTimeout
是异步执行的,它会被放入 WebAPIs 等待指定的时间(这里是 0 毫秒),然后作为宏任务将回调函数放入任务队列。setTimeout(function(){
console.log('setTimeout')
}, 0) -
执行 async1:
async1
是一个异步函数,但其内部的某些操作是同步的。async function async1(){
console.log('async1 start')
await async2()
console.log('async1 end')
}当执行到
async1()
时,首先输出 “async1 start”。然后遇到await async2()
。async2
函数被调用,输出 “async2″,但是因为await
的存在,async1
后续的代码(即 “async1 end”)会被转换为异步操作,放入微任务队列中,等待当前执行栈清空后执行。 -
执行 Promise:创建并执行了一个新的 Promise 对象。
new Promise(function(resolve){
console.log('promise1')
resolve();
}).then(function(){
console.log('promise2')
})在这里,”promise1″ 是同步输出的。而
.then
的回调函数是异步的,会被放入微任务队列,等待当前执行栈清空后执行。 -
同步代码执行完毕:最后一行同步代码执行。
console.log('script end')
这是最后一条同步代码,执行后同步任务结束。
-
执行微任务:此时,执行栈已经清空,事件循环开始处理微任务队列。先执行
async1
的剩余部分(”async1 end”),然后执行 Promise 的.then
回调(”promise2″)。 -
执行宏任务:最后,事件循环处理下一个宏任务,这里是
setTimeout
的回调(”setTimeout”)。
这个顺序是由 JavaScript 的事件循环机制和异步任务(微任务和宏任务)的执行顺序决定的。微任务(如 Promise 回调)总是在当前执行栈清空后立即执行,而宏任务(如 setTimeout 回调)则需要等待下一个事件循环迭代。
怎么样,你分析的结果是否和执行出来的一样呢?
打完收工,关注我,持续分析各种面试题

原文始发于微信公众号(程序员阿凯):面试官:异步编程中的微任务和宏任务分别有哪些常见应用场景
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/174242.html