首先明確兩點:
1.JS 執行機制是單線程。
2.JS的Event loop是JS的執行機制,深入了解Event loop,就等於深入了解JS引擎的執行。
單線程執行帶來什么問題?
在JS執行中都是單線程執行,所以代碼的執行可以說是自上而下,如果前一段的代碼出現問題,就會導致下一段代碼無法執行,對於用戶而言就是卡死現象,所以在JS執行機制引出了異步執行操作。
那異步能解決什么呢問題,又會帶來什么問題?
異步操作能夠很好的解決上面單線程執行出現的卡死現象,但是也會產生問題,比如同時對一件事情操作,不知道應該先執行那件事。
那么同步中使用異步如何實現呢?
是通過的事件循環(Event loop)那先了解下Event loop吧。
先看一下,以下代碼(1)
注意觀察執行順序
console.log("1");
setTimeout(()=>{
console.log("2");
},0);
console.log("3");
有點JS 基礎的同學會容易就知道了運行結果。
運行結果是:1、3、2
大家都知道setTimeout里的函數並沒有立即執行,而是延遲一段時間,符合特定的條件才開始執行,這就是異步執行操作。
由此執行我們先了解到JS任務的執行分類為:同步任務和異步任務。
按照這種的分類方式JS的執行機制是:
首先,判斷JS是同步還是異步,同步進入主線程,異步進入Event table
其次,異步任務在Event table中注冊函數,當滿足特定的條件,被推入Event queue
最后,同步任務進入主線程后一直執行,直到主線程空閑后,才會去Event queue中查看是否有可執行的異步任務,如果有就推入主線程中執行。
循環以上三步執行,這就是Event loop。
所以上面的例子的執行順序是
console.log("1")是同步任務,放入主線程,
setTimeout()是異步任務,被放入Event table,0秒后被推入Event queue里,
console.log("3")是同步任務,放入主線程
當1、3任務先執行完后,主線程去Event queue(事件隊列)里查看是否有可執行的函數,執行setTimeout里的函數。
所以,上面關於Event loop就是我對JS執行機制的理解,直到遇到了下面這段代碼(2)。
setTimeout(()=>{ console.log("定時器開始執行"); }) new Promise(function(resolve){ console.log("准備執行for循環了"); for(var i=0;i<100;i++){ i==22&&resolve(); } }).then(()=>console.log("執行then函數")); console.log("代碼執行完畢");
那我們也按着上面總結的JS執行分類分析:
setTimeout 是異步任務,被放到event table
new Promise 是同步任務,被放到主進程里,直接執行打印 console.log('馬上執行for循環啦') .then里的函數是 異步任務,被放到event table console.log('代碼執行結束')是同步代碼,被放到主進程里,直接執行
所以最后的執行結果是:【准備執行for循環-->代碼執行完畢-->定時器開始執行-->執行then函數 】,對嗎?
最后執行后發現結果並非是你想的這樣,而是【准備執行for循環-->代碼執行完畢-->執行then函數-->定時器開始執行】,為什么會是這樣呢?難道是異步任務執行的順序不是前后順序,而是另有規定?事實上,按照異步同步的划分並不准確。
而准確的方式應該是:
- macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
- micro-task(微任務):Promise,process.nextTic

按照上圖這種分類方式:JS 的執行機制是:
- 執行一個宏任務,過程中如果遇到微任務,就將其放到微任務的【事件隊列】里
- 當前宏任務執行完成后,會查看微任務的【事件隊列】,並將里面全部的微任務依次執行完
重復以上2步驟,結合event loop(1) event loop(2) ,就是更為准確的JS執行機制了。
按照這種執行機制,去分析第二段代碼
首先執行script下的宏任務,遇到setTimeout,將其放到宏任務的【隊列】里 遇到 new Promise直接執行,打印"准備執行for循環"
遇到then方法,是微任務,將其放到微任務的【隊列里】
打印 "代碼執行完畢"
本輪宏任務執行完畢,查看本輪的微任務,發現有一個then方法里的函數, 打印"執行then函數"
到此,本輪的event loop 全部完成。
下一輪的循環里,先執行一個宏任務,發現宏任務的【隊列】里有一個 setTimeout里的函數,執行打印"定時器開始執行"
所以最后的執行順序就是:【准備執行for循環-->代碼執行完畢-->執行then函數-->定時器開始執行】
最后再說一下setTimeout
先看一段代碼
我們一般說這段代碼,在3秒后執行setTimeout內的函數。
setTimeout(function(){ console.log('執行了') },3000)
但這種說法往往是不夠嚴謹的,准確的解釋是:3秒后,setTimeout函數會被推入Event queue(事件隊列),而Event queue(事件隊列)內的任務,只有在主線程空閑時才會執行。
所以,只有同時滿足以下條件setTimeout內的函數才能被執行
1.3秒后
2.主線程空閑時
若主線程的執行任務很多,執行時間超過3秒,比如說5秒,那么setTimeout內的函數只能在5秒后執行了
