站在前人肩膀上能夠看得更遠,也不必定,至少比你本身看得遠。javascript
1、帶着問題去想html
問題:JS爲何是單線程? 爲何須要異步?單線程又是如何實現異步的?java
第一個問題:javaScript最初被設計用在瀏覽器中,思考一下,若是瀏覽器中的JS是多線程,會什麼樣?node
虛擬場景:git
現有2個進程,process1 & process2 ,因爲是多進程的JS,它們對同一個dom同時進行操做,es6
process1 要刪除該dom,process2編輯了該dom元素,同時執行2個命令,試想,瀏覽器究竟要如何執行?github
結論:JS爲何被設計成單線程有木有理解了呢?web
第二個問題: JS爲何須要異步?ajax
虛擬場景:promise
試想,若是JS不存在異步,只能自上而下執行,若是上一行解析時間很長,下面要執行的代碼就會被阻塞。
對於用戶而言,阻塞意味着卡死,瀏覽器無響應,等等。。 致使用戶體驗不好,估計研發要和(產品、UI設計、視覺設計等一票人)幹一仗了。
結論:證實JS中是必定要存在異步執行操做。
第三個問題:試想單線程要如何實現異步操做?
結論:經過事件循環(Event Loop)理解event loop機制,一樣就理解了JS的執行機制
2、概念
再講以前,咱們先熟悉瞭解一下,3個概念?
事件循環:將任務棧中的事件放到執行棧中
事件循環過程被稱爲"tick"; (每次Tick會查看任務隊列中是否有須要執行的任務)
每次Tick的過程就是查看是否有待處理的事件,若是有則取出相關事件及回調函數放 入執行棧中由主線程執行。
任務隊列(事件隊列):存儲異步操做添加的相關事件回調
異步操做會將相關回調添加到任務隊列中。不一樣的異步操做添加到任務隊列的時機也不一樣。
異步操做是由瀏覽器內核的 webcore 來執行的,webcore 包含3種 webAPI,分別是 DOM Binding、network、timer模塊
{
1.setTimeout 會由瀏覽器內核的 timer 模塊來進行延時處理,當時間到達的時候,纔會將回調函數添加到任務隊列中。
2.onclick 由瀏覽器內核的 DOM Binding 模塊來處理,當事件觸發的時候,回調函數會當即添加到任務隊列中。
3.ajax(XMLHttpRequest) 則會由瀏覽器內核的 network 模塊來處理,在網絡請求完成返回以後,纔將回調添加到任務隊列中。
}
主線程:
1. 主線程中的執行棧放的同步代碼,先執行
2. 主線程惟一(只有一個主線程)主線程將執行棧裏的代碼執行完畢。事件循環纔開始執行
3. 主線程中執行棧爲空,纔會進行事件循環來觀察要執行的事件回調。當事件循環檢測到任務隊列中有事件就取出相關回調放入執行棧中由主線程執行
此圖來源網絡,再次引用做爲學習用途,感謝原圖做者繪圖!
具體只一個任務隊列來講明:
ok
3、Event Loop 運行機制
先看一個例子:輸出的循序是什麼?
console.log(1);
setTimeout(() => {
console.log(2)
}, 1000)
console.log(3);
分析:setTimeout中的函數並無當即執行,二是延遲了一段時間,知足必定條件後,才執行,一般咱們所謂的異步操做。
注:setTimeout函數,一般理解爲1秒後,執行裏面的函數,這種理解並非很準確,
而是,1秒後,setTimeout中函數會推入到event queue,而事件隊列中的任務,只有在 主線程空閒時纔會執行。
只有同時知足:1秒後且主線程空閒時,纔會真的在1秒後執行該函數
實際場景,會遇到,超過1秒後,才執行該函數(由於主線程執行的內容不少,致使真正的延遲時間遠遠大於1秒)
特殊狀況:setTimeout(fn,0)
只要主線程執行棧內的同步任務所有執行完成,棧爲空就立刻執行;
即使主線程爲空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒
故此,知道JS中將任務分爲同步任務和異步任務。
看圖:
JS執行的機制是:
1.先判斷JS是同步任務仍是異步任務,同步任務進入主進程,異步任務進入event table
2.異步任務在event table中註冊函數,當知足條件後,被推入event queue
3.同步任務進入主線程後一直執行,當主線程空閒時,纔會去event queue中查看是否有可執行的異步任務,若是有就推入主線程中(執行棧)
上述步驟循環執行,即event loop
注:event table就是註冊站,調用棧讓event table註冊一個函數,並移到event queue。
event queue:理解爲緩衝區域便可,事件隊列中函數等着被調用並移到調用棧(執行棧)。
JS引擎會有一個monitoring process 持續不斷地檢查調用棧是否爲空
回過頭,分析上述例子:
console.log(1); // 同步任務,放入主線程
setTimeout() 是異步任務,被放入event table, 0秒以後被推入event queue裏
console.log(3 是同步任務,放到主線程裏
當 1、 3在控制條被打印後,主線程去event queue(事件隊列)裏查看是否有可執行的函數,執行setTimeout裏的函數
繼續往下看:
setTimeout(function(){
console.log('定時器開始啦')
});
new Promise(function(resolve){
console.log('立刻執行for循環啦');
for(var i = 0; i < 10; i++){
i == 9 && resolve();
}
}).then(function(){
console.log('執行then函數啦')
});
console.log('代碼執行結束');
按照上述JS執行機制分析,最終輸出結果
1.setTimeout 是異步任務,被放到event table
2.new Promise 是同步任務,被放到主進程裏,直接執行打印 console.log('立刻執行for循環啦')
3..then裏的函數是 異步任務,被放到event table
4.console.log('代碼執行結束')是同步代碼,被放到主進程裏,直接執行
結論:立刻執行for循環啦 — 代碼執行結束 — 定時器開始啦 — 執行then函數啦(對麼)
按照異步和同步的劃分方式,並不許確
劃分方式:任務隊列分兩種類型(宏任務、微任務)
注:ES6 的 promise[ECMAScript標準]產生的任務隊列爲microtask queue
按照此分類,JS執行的機制是:
此時,再次分析上述例子:
結論:立刻執行for循環啦 — 代碼執行結束 — 執行then函數啦 — 定時器開始啦
最後,
總結:
永遠記得js就是單線程執行
(擴展:h5中 webworker可實現多線程)主要解決計算,並不能操做dom
具體可參考:http://www.ruanyifeng.com/blog/2018/07/web-worker.html
1.瞭解事件循環的機制,瞭解了任務隊列、js主線程、異步操做之間的相互協做;
2.瞭解兩種任務隊列:macrotask queue & microtask queue
microtask queue對應ECMAScript的promise屬性(es6)DOM3的MutationObserver
3.經過JS的事件循環機制,能夠清楚js代碼的執行流,更好的控制代碼
不夠完善、不夠詳細、後續不斷調整補充
看到一句總結關於事件隊列的優先級的問題!
在JS中ES6 中新增的任務隊列(promise)是在事件循環之上的,事件循環每次 tick 後會查看 ES6 的任務隊列中是否有任務要執行,也就是 ES6 的任務隊列比事件循環中的任務(事件)隊列優先級更高。而 Promise 就使用了 ES6 的任務隊列特性,也即在執行完任務棧後首先執行的是任務隊列中的promise任務。其餘異步操做加入隊列的時間是沒有相應優先級一說。
參考資料: