靜下心學(xué)了一波事件循環(huán)機(jī)制,好開心,我學(xué)會(huì)了,首先還是得感謝作者寫的筆記特別詳細(xì) 鏈接: http://www.cnblogs.com/lsgxeva/p/7976217.html
javascript的特點(diǎn)之一就是單線程,而這個(gè)線程擁有唯一的一個(gè)事件循環(huán).javascript代碼在執(zhí)行過(guò)程中,除了依靠函數(shù)調(diào)用棧來(lái)搞定函數(shù)的執(zhí)行順序外,還依靠任務(wù)隊(duì)列(task queue)來(lái)搞定另外一些代碼的執(zhí)行.
隊(duì)列數(shù)據(jù)結(jié)構(gòu)
一個(gè)線程中,事件循環(huán)是唯一的,但是任務(wù)隊(duì)列可以擁有多個(gè).
任務(wù)隊(duì)列分為macro-task(宏任務(wù))與micro-task(微任務(wù)),
macro-task大概包括: script(整體代碼),setTimeout,setInterval,setImmediate,I/o, UI rendering
micro-task大概包括: process.nextTick, promise, object.observe(已廢棄) mutationObserver(html5新特性).
setTimeout/Promise等我們稱之為任務(wù)源.而進(jìn)行任務(wù)隊(duì)列的是他們指定的具體執(zhí)行任務(wù).
來(lái)自不同任務(wù)源的任務(wù)會(huì)進(jìn)入到不同的任務(wù)隊(duì)列.其中setTimeout與setInterval是同源.
事件循環(huán)的順序,決定了javascript代碼的執(zhí)行順序.它從script(整體代碼)開始第一次循環(huán).之后全局上下文進(jìn)入函數(shù)調(diào)用棧.直到函數(shù)調(diào)用棧(只剩全局),然后執(zhí)行所有的micro-task.當(dāng)所有可執(zhí)行的micro-task執(zhí)行完畢之后.循環(huán)再次從macro-task開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢,然后再執(zhí)行所有的micro-task,這樣一直循環(huán)下去
以下舉個(gè)例子:

首先,事件循環(huán)從宏任務(wù)隊(duì)列開始,宏任務(wù)隊(duì)列中,只有一個(gè)script(整體代碼)任務(wù).每一個(gè)任務(wù)的執(zhí)行順序,都依靠函數(shù)調(diào)用棧來(lái)搞定,而當(dāng)遇到任務(wù)源時(shí),則會(huì)先分發(fā)任務(wù)到相應(yīng)的隊(duì)列中去,所以第一步如下圖

首先script任務(wù)開始執(zhí)行,全局上下文入棧,第二步:script任務(wù)執(zhí)行時(shí)首先遇到了setTimeout,setTimeout為一個(gè)宏任務(wù)源,那么他的作用就是將任務(wù)分發(fā)到它對(duì)應(yīng)的隊(duì)列中.

第三步: script執(zhí)行時(shí)遇到promise實(shí)例.promise構(gòu)造函數(shù)中的第一個(gè)參數(shù),是在new的時(shí)候執(zhí)行,因此不會(huì)進(jìn)入任何其他的隊(duì)列,而是直接在當(dāng)前任務(wù)直接執(zhí)行了.而后續(xù)的.then則會(huì)分發(fā)到micro-task的promise隊(duì)列中去.
因此,構(gòu)造函數(shù)執(zhí)行時(shí),里面的參數(shù)進(jìn)入函數(shù)調(diào)用棧執(zhí)行.for循環(huán)不會(huì)進(jìn)行任何隊(duì)列,因此代碼依次執(zhí)行,所以這里的promise1和promise2會(huì)依次輸出.



構(gòu)造函數(shù)執(zhí)行完畢的過(guò)程中,resolve執(zhí)行完畢出棧,promise2輸出,promise1頁(yè)出棧,then執(zhí)行時(shí),promise任務(wù)then1進(jìn)入對(duì)應(yīng)隊(duì)列.
script任務(wù)繼續(xù)往下執(zhí)行,最后只有一句輸出global1,然后,全局任務(wù)就執(zhí)行完畢了.
第四步: 第一個(gè)宏任務(wù)script執(zhí)行完畢之后,就開始執(zhí)行所有的可執(zhí)行的微任務(wù).這個(gè)時(shí)候,微任務(wù)中,只有promise隊(duì)列中的一個(gè)任務(wù)then1,因此直接執(zhí)行就行了,執(zhí)行結(jié)果輸出then1,當(dāng)然,他的執(zhí)行也是進(jìn)入函數(shù)調(diào)用棧中執(zhí)行的

第5步:當(dāng)所有的micro-task執(zhí)行完畢之后,表示第一輪的循環(huán)就結(jié)束了.這個(gè)時(shí)候就得開始第二輪的循環(huán).第二輪循環(huán)仍然從宏任務(wù)macro-task開始

這個(gè)時(shí)候,我們發(fā)現(xiàn)宏任務(wù)中,只有setTimeout隊(duì)列中還要一個(gè)timeout的任務(wù)等待執(zhí)行.因此就直接執(zhí)行即可.

這個(gè)時(shí)候宏任務(wù)隊(duì)列與微任務(wù)隊(duì)列中都沒(méi)有任務(wù)了,所以代碼就不會(huì)在輸出其他東西了.下面有一個(gè)更加難的栗子,幫助更加的理解.
// demo02console.log('golb1');setTimeout(function() {console.log('timeout1'); process.nextTick(function() {console.log('timeout1_nextTick'); })newPromise(function(resolve) {console.log('timeout1_promise'); resolve(); }).then(function() {console.log('timeout1_then') })})setImmediate(function() {console.log('immediate1'); process.nextTick(function() {console.log('immediate1_nextTick'); })newPromise(function(resolve) {console.log('immediate1_promise'); resolve(); }).then(function() {console.log('immediate1_then') })})process.nextTick(function() {console.log('glob1_nextTick');})newPromise(function(resolve) {console.log('glob1_promise'); resolve();}).then(function() {console.log('glob1_then')})setTimeout(function() {console.log('timeout2'); process.nextTick(function() {console.log('timeout2_nextTick'); })newPromise(function(resolve) {console.log('timeout2_promise'); resolve(); }).then(function() {console.log('timeout2_then') })})process.nextTick(function() {console.log('glob2_nextTick');})newPromise(function(resolve) {console.log('glob2_promise'); resolve();}).then(function() {console.log('glob2_then')})setImmediate(function() {console.log('immediate2'); process.nextTick(function() {console.log('immediate2_nextTick'); })newPromise(function(resolve) {console.log('immediate2_promise'); resolve(); }).then(function() {console.log('immediate2_then')
})
})
第一步: 宏任務(wù)script首先執(zhí)行.全局入棧.globl1輸出.

第二步,執(zhí)行過(guò)程遇到setTimeout.setTimeout作為任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的宏任務(wù)隊(duì)列中.

第三部:執(zhí)行過(guò)程遇到setImmediate setImmediate也是一個(gè)宏任務(wù)分發(fā)器,將任務(wù)分發(fā)到對(duì)應(yīng)的任務(wù)隊(duì)列中.setImmediate的任務(wù)隊(duì)列在setTimeout隊(duì)列的后面執(zhí)行.

第四步: 執(zhí)行遇到nextTick,process.nextTick是一個(gè)微任務(wù)分發(fā)器,它會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中去.

第五步:執(zhí)行遇到promise.promise的then方法會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的微任務(wù)隊(duì)列中,但是它構(gòu)造函數(shù)中的方法直接執(zhí)行.因此,globl_promise會(huì)第二輸出.


第六步: 執(zhí)行遇到第二個(gè)setTimeout

第七步:先后遇到nextTick與Promise

第八步:再次遇到setImmediate

這個(gè)時(shí)候,script中代碼就執(zhí)行完畢了,執(zhí)行過(guò)程中,遇到不同的任務(wù)分發(fā)器,就將任務(wù)分發(fā)到各自對(duì)應(yīng)的隊(duì)列中去,接下來(lái),將會(huì)執(zhí)行所有的微任務(wù)隊(duì)列中的任務(wù).
其中,nextTick隊(duì)列會(huì)比promise先執(zhí)行.nextTick中的可執(zhí)行任務(wù)完畢之后,才會(huì)開始執(zhí)行promise隊(duì)列中任務(wù),當(dāng)所有可執(zhí)行的微任務(wù)執(zhí)行完畢之后,這一輪循環(huán)就表示結(jié)束了.下一輪循環(huán)繼續(xù)從宏任務(wù)隊(duì)列開始執(zhí)行.這個(gè)時(shí)候,script已經(jīng)執(zhí)行完畢,所以就從setTimeout隊(duì)列開始執(zhí)行.

setTimeout任務(wù)的執(zhí)行,也是依然是借助函數(shù)調(diào)用棧來(lái)完成,并且遇到任務(wù)分發(fā)器的時(shí)候也會(huì)將任務(wù)分發(fā)到對(duì)應(yīng)的隊(duì)列中去.
只有當(dāng)setTimeout中所有的任務(wù)執(zhí)行完畢之后,才會(huì)再次開始執(zhí)行微任務(wù)隊(duì)列.并且清空所有的可執(zhí)行微任務(wù).
大家需要注意這里的循環(huán)結(jié)束的時(shí)間節(jié)點(diǎn)
當(dāng)我們執(zhí)行setTimeout任務(wù)中遇到setTimeout時(shí),它仍然會(huì)將對(duì)應(yīng)的任務(wù)分發(fā)到setTimeout隊(duì)列中去,但是該任務(wù)就得到等到下一輪事件循環(huán)執(zhí)行了.栗子中還沒(méi)有涉及到這么復(fù)雜的嵌套,可以動(dòng)手添加或者修改他們的位置感受一下.
總結(jié)到這.