既然今天要談的是javascript的事件循環機制,要理解事件循環,首先要知道事件循環是什麼。javascript
咱們先從一個例子來看一下javascript的執行順序。html
<script> setTimeout(function() { console.log('定時器開始了.'); },0) new Promise(function(resolve) { console.log('立刻執行for循環了'); for (let i = 0; i < 10000; i++) { i == 99 && resolve(); } }).then(function() { console.log('執行then函數了'); }) console.log('代碼執行結束'); //執行結果爲: //立刻執行for循環了 //代碼執行結束 //執行then函數了 //定時器開始了. </script>
怎麼樣,是否是和本身在內心運行的結果差了一萬八千里呢。若是是的話,請耐心看完後面的內容,讓你完全弄明白javascript的事件循環機制。java
要想了解事件循環的咱們就得從javascript的工做原理開始提及。程序員
javascript語言的一大特色就是單線程,但是爲何javascript不作成多線程呢?ajax
JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?編程
咱們說單線程就意味着全部的任務必須排隊。就相似於銀行只有一個窗口,前一個任務執行結束後,後一個任務才能執行。若是新執行的任務耗時很長,那麼後一個任務就不得不一直等着。瀏覽器
這樣就又出現了一個問題,在進行瀏覽器的操做時,咱們經常會經過ajax向後臺發送請求,然而js必須等到瀏覽器接收到響應內容後纔會繼續往下執行,若是這段時間是10s,那麼頁面必須停在這裏10s。這不只會影響用戶體驗,也會下降CPU的利用率,顯然不是咱們想要的。多線程
因而,聰明的程序員小哥哥就把任務分紅了兩類異步
同步任務指的是:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步編程
異步任務指的是:不進入主線程、而進入其餘線程的任務(好比處理事件的事件觸發線程,處理HTTP請求的異步HTTP請求線程,處理定時器的定時器觸發線程),當任務完成後,相應的線程會把對應的回調函數放置到任務隊列中,一旦執行棧
中的全部同步任務執行完畢(此時JS引擎空閒),系統就會讀取任務隊列
,將可運行的異步任務添加到可執行棧中,開始執行。
同步任務和異步任務的執行過程大體能夠簡化成以下的導圖所示。
<script> console.log(1); setTimeout(function task() {
console.log('定時器執行了.');
},1000);
console.log(2); </script>
咱們不由要問了,那怎麼知道主線程執行棧爲空啊?js引擎存在monitoring process(這個不知道翻譯成啥好,由於翻譯成監聽進程也不對),會持續不斷的檢查主線程執行棧是否爲空,一旦爲空,就會去Event Queue那裏檢查是否有等待被調用的函數。
其實除了廣義的同步任務和異步任務的劃分,異步任務還能夠細分爲宏任務(macrotask) 與微任務( microtask)。不一樣的異步任務類型會進入不一樣的Event Queue。
宏任務: 須要屢次事件循環才能執行完,事件隊列中的每個事件對應的回調函數都是一個宏任務,每次事件循環都會調入一個宏任務;
瀏覽器爲了可以使得js內部宏任務與DOM任務有序的執行,會在一個宏任務執行結束後,在下一個宏執行開始前,對頁面進行從新渲染 (task->渲染->task->…)。
例如鼠標點擊會觸發一個事件回調,須要執行一個宏任務,而後從新渲染頁面;setTimeout的做用是等待給定的時間後爲它的回調產生一個新的宏任務。
微任務: 微任務是一次性執行完的。微任務一般來講是須要在當前task執行結束後當即執行的任務,例如對一些動做作出反饋或者異步執行任務又不須要分配一個新的task,這樣即可以提升一些性能。只要執行棧中沒有其餘的js代碼正在執行了,並且當前調入的宏任務都執行完了,微任務隊列會當即執行。若是在微任務執行期間微任務隊列加入了新的微任務,會將新的微任務加入隊列尾部,以後也會被執行。
簡單理解,宏任務在下一輪事件循環執行,微任務在本輪事件循環的全部任務結束後從新渲染前執行。
據whatwg規範介紹:
宏任務、微任務執行流程圖以下所示。
這時,咱們再來看一下文章開頭給的一段代碼。
<script> setTimeout(function() { console.log('定時器開始了.'); },0) new Promise(function(resolve) { console.log('立刻執行for循環了'); for (let i = 0; i < 10000; i++) { i == 99 && resolve(); } }).then(function() { console.log('執行then函數了'); }) console.log('代碼執行結束'); //執行結果爲: //立刻執行for循環了 //代碼執行結束 //執行then函數了 //定時器開始了. </script>
執行步驟以下所示。
javascript是一門單線程的語言,事件循環是js異步編程的一種方法。也是js的執行機制。當瀏覽器中的網頁剛剛載入的時候,<script>裏的代碼會做爲第一個宏任務被壓入棧執行,同步代碼執行完後,若是有微任務就執行微任務,沒有微任務就執行下一個宏任務。如此往復循環,直至全部任務都執行完畢。