1、JS單線程、異步、同步概念
從上一篇說明vue nextTick的文章中,屢次出現「事件循環」這個名詞,簡單說明了事件循環的步驟,以便理解nextTick的運行時機,這篇文章將更爲詳細的分析下事件循環。在此以前須要瞭解JS單線程,及由此產生的同步執行環境和異步執行環境。
衆所周知,JS是單線程(若是一個線程刪DOM,一個線程增DOM,瀏覽器傻逼了~因此只能單着了),雖然有webworker醬紫的多線程出現,但也是在主線程的控制下。webworker僅僅能進行計算任務,不能操做DOM,因此本質上仍是單線程。
單線程即任務是串行的,後一個任務須要等待前一個任務的執行,這就可能出現長時間的等待。但因爲相似ajax網絡請求、setTimeout時間延遲、DOM事件的用戶交互等,這些任務並不消耗 CPU,是一種空等,資源浪費,所以出現了異步。經過將任務交給相應的異步模塊去處理,主線程的效率大大提高,能夠並行的去處理其餘的操做。當異步處理完成,主線程空閒時,主線程讀取相應的callback,進行後續的操做,最大程度的利用CPU。此時出現了同步執行和異步執行的概念,同步執行是主線程按照順序,串行執行任務;異步執行就是cpu跳過等待,先處理後續的任務(CPU與網絡模塊、timer等並行進行任務)。由此產生了任務隊列與事件循環,來協調主線程與異步模塊之間的工做。
2、事件循環機制
事件循環示例圖
如上圖爲事件循環示例圖(或JS運行機制圖),流程以下:
step1:主線程讀取JS代碼,此時爲同步環境,造成相應的堆和執行棧;
step2: 主線程遇到異步任務,指給對應的異步進程進行處理(WEB API);
step3: 異步進程處理完畢(Ajax返回、DOM事件處罰、Timer到等),將相應的異步任務推入任務隊列;
step4: 主線程執行完畢,查詢任務隊列,若是存在任務,則取出一個任務推入主線程處理(先進先出);
step5: 重複執行step二、三、4;稱爲事件循環。
執行的大意:
同步環境執行(step1) -> 事件循環1(step4) -> 事件循環2(step4的重複)…
其中的異步進程有:
a、相似onclick等,由瀏覽器內核的DOM binding模塊處理,事件觸發時,回調函數添加到任務隊列中;
b、setTimeout等,由瀏覽器內核的Timer模塊處理,時間到達時,回調函數添加到任務隊列中;
c、Ajax,由瀏覽器內核的Network模塊處理,網絡請求返回後,添加到任務隊列中。
3、任務隊列
如上示意圖,任務隊列存在多個,同一任務隊列內,按隊列順序被主線程取走;不一樣任務隊列之間,存在着優先級,優先級高的優先獲取(如用戶I/O);
3.一、任務隊列的類型
任務隊列存在兩種類型,一種爲microtask queue,另外一種爲macrotask queue。
圖中所列出的任務隊列均爲macrotask queue,而ES6 的 promise[ECMAScript標準]產生的任務隊列爲microtask queue。
3.二、二者的區別
microtask queue:惟一,整個事件循環當中,僅存在一個;執行爲同步,同一個事件循環中的microtask會按隊列順序,串行執行完畢;
macrotask queue:不惟一,存在必定的優先級(用戶I/O部分優先級更高);異步執行,同一事件循環中,只執行一個。
3.三、更完整的事件循環流程
將microtask加入到JS運行機制流程中,則:
step一、二、3同上,
step4:主線程查詢任務隊列,執行microtask queue,將其按序執行,所有執行完畢;
step5:主線程查詢任務隊列,執行macrotask queue,取隊首任務執行,執行完畢;
step6:重複step四、step5。
microtask queue中的全部callback處在同一個事件循環中,而macrotask queue中的callback有本身的事件循環。
簡而言之:同步環境執行 -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個) -> 事件循環1(microtask queue的All)-> 事件循環2(macrotask queue中的一個)...
利用microtask queue能夠造成一個同步執行的環境,但若是Microtask queue太長,將致使Macrotask任務長時間執行不了,最終致使用戶I/O無響應等,因此使用需慎重。
4、示例、驗證
console.log('1, time = ' + new Date().toString())
setTimeout(macroCallback, 0);
new Promise(function(resolve, reject) {
console.log('2, time = ' + new Date().toString())
resolve();
console.log('3, time = ' + new Date().toString())
}).then(microCallback);
function macroCallback() {
console.log('4, time = ' + new Date().toString())
}
function microCallback() {
console.log('5, time = ' + new Date().toString())
}
結合第二節與第三節的分析,此處的執行流程應爲:vue
同步環境:1 -> 2 -> 3web
事件循環1(microCallback):5ajax
事件循環2(macroCallback):4promise
運行結果以下:瀏覽器
運行結果與預期一致,驗證了在不一樣類型的任務隊列中,microtask queue中的callball將優先執行。網絡
總結:由此咱們瞭解事件循環的機制,同時瞭解了任務隊列、JS主線程、異步操做之間的相互協做;同時認識了兩種任務隊列:macrotask queue、microtask queue,它們由不一樣的標準制定,microtask queue對應ECMAScript的promise屬性(ES6)和 DOM3的MutationObserver,文中說明了二者在事件循環中的運行狀況及區別;在從此的異步操做中,經過靈活運用不一樣的任務隊列,提高用戶交互性能,給出更加的響應和視覺體驗;同時,經過JS的事件循環機制,能夠更清楚JS代碼的執行流,從而更好的控制代碼,更有效、更好的爲業務服務。