js是一门单线程的编程语言,也就是说js在处理任务的时候,全部任务只能在一个线程上排队被执行,那若是某一个任务耗时比较长呢?总不能等到它执行结束再去执行下一个。
因此在线程以内,又被分为了两个队列:html
举个例子来讲:好比你去银行办理业务,都须要领号排队。银行柜员一个个办理业务,这时这个柜员就至关于一个js线程,客户排的队就至关于同步任务队列,每一个人对于柜员至关于一个个的任务。
但这个时候,你的电话忽然响了,你去接电话接了半小时。这时候人家柜员一看你这状况,直接叫了下一个,而你领的号就做废了,只能从新零号排队。这时候你就是被分发到了异步任务队列。
等你前边的人都完事了,柜员把你叫过去办了你的业务,这时候就是同步队列中的任务执行完了,主线程会处理异步队列中的任务。前端
这里说的异步任务,它的意思是包含了独立于主执行栈以外的宏任务和微任务
。html5
先看一个简单的例子,对这样的执行机制有个简单的认识:面试
console.log('start') console.log('end')
上边的执行结果你们确定都明白,先输出start,再输出end,这一段代码会进入同步队列,顺序执行。ajax
那么咱们加点料:编程
console.log('start') setTimeout(function() { console.log('setTimeout') }, 0) console.log('end')
这样的状况,函数调用栈执行到setTimeout时,setTimeout会在规定的时间点将回调函数放入异步队列,等待同步队列的任务被执行完,当即执行,因此结果是:start、end、setTimeout。promise
但须要注意的一点是,广泛认为setTimeout定时执行的认知是片面的,由于假设setTimeout规定2秒后执行,但同步队列中有一个函数,执行花了很长时间,甚至花了1秒。那么这时setTimeout中的回调也会等上至少1秒以后,同步任务都执行完了,再去执行。这时候的setTimeout回调执行的时机就会超过2秒,也就是至少3秒。异步
宏任务与微任务都是独立与主执行栈以外的另外两个队列,能够在概念上划分在异步任务队列里。而这些队列由js的事件循环(EventLoop)来搞定编程语言
macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
因为写文章时没有注意到,实际上宏任务与微任务的概念是不许确的,但因为文章中涉及多处宏任务、微任务的解读,因此本文暂时仍是用宏任务、微任务来分别代指task、jobs。但读者要明白规范中没有宏任务的概念,只有task与jobs函数
其中宏任务(task)包括:
ajax请求不属于宏任务,js线程遇到ajax请求,会将请求交给对应的http线程处理,一旦请求返回结果,就会将对应的回调放入宏任务队列,等请求完成执行。
微任务(jobs)包括:
这些咱们能够理解为它们在执行上下文中都是可执行代码,会当即执行,只不过会将各自的回调函数放入对应的任务队列中(宏任务微任务),也就至关于一个调度者。
咱们梳理一下事件循环的执行机制:
循环首先从宏任务开始,遇到script,生成执行上下文,开始进入执行栈,可执行代码入栈,依次执行代码,调用完成出栈。
执行过程当中遇到上边提到的调度者,会同步执行调度者,由调度者将其负责的任务(回调函数)放到对应的任务队列中,直到主执行栈清空,而后开始执行微任务的任务队列。微任务也清空后,再次从宏任务开始,一直循环这一过程。
上边说了那么多,仍是用一些代码来验证一下是不是这样的,先来一个简单一点的。
console.log('start') setTimeout(function() { console.log('timeout') }, 0) new Promise(function(resolve) { console.log('promise') resolve() }).then(function() { console.log('promise resolved') }) console.log('end')
根据上边的结论,分析一下执行过程:
start
promise
,可是当resolve后,.then会把其内部的回调函数放入微任务队列end
。这时,主执行栈清空了,开始寻找微任务队列里有没有可执行代码promise resolved
,第一次循环结束timeout
因此,打印顺序是:start
-->promise
-->end
-->promise resolved
-->timeout
上边是一个简单示例,比较好理解。那么接下来看一个稍微复杂一点的(这里直接用汉字直观地代表了打印的时机,避免看起来费劲):
console.log('第一次循环主执行栈开始') setTimeout(function() { console.log('第二次循环开始,宏任务队列的第一个宏任务执行中') new Promise(function(resolve) { console.log('宏任务队列的第一个宏任务的微任务继续执行') resolve() }).then(function() { console.log('第二次循环的微任务队列的微任务执行') }) }, 0) new Promise(function(resolve) { console.log('第一次循环主执行栈进行中...') resolve() }).then(function() { console.log('第一次循环微任务,第一次循环结束') setTimeout(function() { console.log('第二次循环的宏任务队列的第二个宏任务执行') }) }) console.log('第一次循环主执行栈完成')
一样咱们分析一下执行过程:
第一次循环
第一次循环主执行栈开始
第一次循环主执行栈进行中...
,resolve后遇到.then,将回调放入微任务队列第一次循环主执行栈完成
第一次循环微任务,第一次循环结束
,第一次循环结束,同时遇到setTimeout,将回调放入宏任务队列第二次循环
第二次循环开始,宏任务队列的第一个宏任务执行中
宏任务队列的第一个宏任务继续执行
,这时候又被resolve了,又会将.then中的回调放入微任务队列,这是这个宏任务队列中的第一个任务还没执行完第二次循环的微任务队列的微任务执行
,此时第一个宏任务执行完毕第二次循环的宏任务队列的第二个宏任务执行
,全部任务队列所有清空,执行完毕因此打印顺序为:
看一下gif,事件循环以肉眼可见的形式呈现出来(两次循环之间有微小的时间间隔)
js的执行机制是面试中常考的点,也是很是绕的。但相信彻底了解事件循环机制,仔细分析的话,面试遇到这样的题彻底不是问题。我在写这篇文章的时候,发现本身以前理解的很大一部分是错的。若是你们以为哪里有错误,还请帮忙指点出来。
欢迎关注个人公众号: 一口一个前端,不按期分享我所理解的前端知识