javaScript的特色就是單線程,在這個線程中擁有惟一的一個事件循環。html
爲何JS是單線程的??html5
JS的主要用途就是與用戶交互,操做DOM,假如JS同時有兩個線程,一個線程中在某個DOM節點上添加內容,另外一個線程須要執行刪除該節點操做,就會產生衝突。java
事件循環機制告訴咱們JavaScript的執行順序。promise
單線程意味着全部任務都須要排隊,前一任務結束,纔會執行後一個任務,若是前一個任務耗時很長,後一個任務就不得不一直等着。異步
JS引擎執行異步代碼不用等待,是由於有事件隊列和事件循環。async
事件循環是指主線程重複從事件隊列中取消息、執行的過程。指整個執行流程。函數
事件隊列是一個存儲着待執行任務的序列,其中的任務嚴格按照時間前後順序執行,排在隊頭的任務會率先執行,而排在隊尾的任務會最後執行。spa
事件隊列:線程
宏任務能夠理解成每次執行棧執行的代碼就是一個宏任務。code
事件循環運行機制
(1)執行一個宏任務(棧中沒有就從事件隊列中獲取)
(2)執行過程當中若是遇到微任務,就將它添加到微任務的任務隊列中;
(3)宏任務執行完畢後,當即執行當前微任務隊列的全部微任務;
(4)當前微任務執行完畢,開始檢查渲染,而後GUI線程接管渲染;
(5)渲染完畢後,JS線程繼續接管,開始下一個宏任務。
例:
async function async1() { console.log("async1 start"); //(2) await async2(); console.log("async1 end"); //(6) } async function async2() { console.log( 'async2'); //(3) } console.log("script start"); //(1) setTimeout(function () { console.log("settimeout"); //(8) },0); async1(); new Promise(function (resolve) { console.log("promise1"); //(4) resolve(); }).then(function () { console.log("promise2"); //(7) }); console.log('script end');//(5)
先按順序執行同步代碼 從‘script start‘開始,
執行到setTimeout函數時,將其回調函數加入隊列(此隊列與promise隊列不是同一個隊列,執行的優先級低於promise。
而後調用async1()方法,await async2();//執行這一句後,輸出async2後,await會讓出當前線程,將後面的代碼加到任務隊列中,而後繼續執行test()函數後面的同步代碼
繼續執行建立promise對象裏面的代碼屬於同步代碼,promise的異步性體如今then與catch處,因此promise1被輸出,而後將then函數的代碼加入隊列,繼續執行同步代碼,輸出script end。至此同步代碼執行完畢。
開始從隊列中調取任務執行,因爲剛剛提到過,setTimeout的任務隊列優先級低於promise隊列,因此首先執行promise隊列的第一個任務,由於在async函數中有await表達式,會使async函數暫停執行,等待表達式中的 Promise 解析完成後繼續執行 async 函數並返回解決結果。
因此先執行then方法的部分,輸出promise2,而後執行async1中await後面的代碼,輸出async1 end。。最後promise隊列中任務執行完畢,再執行setTimeout的任務隊列,輸出settimeout。
setTimeout(fn,0)的含義是指某個任務在主線程最先可得的空閒時間執行。它在「任務隊列」的尾部添加一個事件,所以要等到同步任務和「任務隊列」現有的時間處理完纔會獲得執行。
按照事件循環機制分析以上代碼運行流程:
1. 首先,事件循環從宏任務(macrotask)隊列開始,這個時候,宏任務隊列中,只有一個script(總體代碼)任務;當遇到任務源(task source)時,則會先分發任務到對應的任務隊列中去。
2. 而後咱們看到首先定義了兩個async函數,接着往下看,而後遇到了 `console` 語句,直接輸出 `script start`。輸出以後,script 任務繼續往下執行,遇到 `setTimeout`,其做爲一個宏任務源,則會先將其任務分發到對應的隊列中。
3. script 任務繼續往下執行,執行了async1()函數,前面講過async函數中在await以前的代碼是當即執行的,因此會當即輸出`async1 start`。
遇到了await時,會將await後面的表達式執行一遍,因此就緊接着輸出`async2`,而後將await後面的代碼也就是`console.log('async1 end')`加入到microtask中的Promise隊列中,接着跳出async1函數來執行後面的代碼。
4. script任務繼續往下執行,遇到Promise實例。因爲Promise中的函數是當即執行的,然後續的 `.then` 則會被分發到 microtask 的 `Promise` 隊列中去。因此會先輸出 `promise1`,而後執行 `resolve`,將 `promise2` 分配到對應隊列。
5. script任務繼續往下執行,最後只有一句輸出了 `script end`,至此,全局任務就執行完畢了。
根據上述,每次執行完一個宏任務以後,會去檢查是否存在 Microtasks;若是有,則執行 Microtasks 直至清空 Microtask Queue。
於是在script任務執行完畢以後,開始查找清空微任務隊列。此時,微任務中, `Promise` 隊列有的兩個任務`async1 end`和`promise2`,所以按前後順序輸出 `async1 end,promise2`。當全部的 Microtasks 執行完畢以後,表示第一輪的循環就結束了。
6. 第二輪循環依舊從宏任務隊列開始。此時宏任務中只有一個 `setTimeout`,取出直接輸出便可,至此整個流程結束。