setTimeout(function() { console.log(111); }, 0); // 這裏定時器時間設置爲0ms後執行 console.log(222);
相信這道題不少人都看過,結果是先輸出222
,再輸出111
可能新手會犯錯,認爲定時器設置0毫秒就等於當即就執行,因此先輸出111
。但其實內部涉及一個很重要的JS運行機制,也就是咱們今天的主角——事件輪詢(Event Loop)前端
在聊Event Loop以前,有必要先講講JS的一些重要特色面試
JS的一大特色就是單線程,也就是說,同一個時間只能作一件事。那麼,爲何JS不能有多個線程呢? 瀏覽器
第一,爲了提升效率,減小CPU的開銷。在多線程中,CPU須要來回切換線程,就會存在線程切換上的開銷。 網絡
第二,JS最初設計時,是做爲瀏覽器的腳本語言,主要用途是與用戶互動,以及操做DOM。這就決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JS同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?多線程
說到JS的異步,可能有同窗會問啦,JS是單線程的怎麼還能異步執行,這不是自相矛盾嗎?的確,單線程和異步確實不能同時成爲一個語言的特性,因此它自己不多是異步的。必定是存在一種機制讓它可以異步執行,往下看!異步
JS是單線程就意味着,全部任務須要排隊,等前一個任務結束,才能執行後一個任務。但前端的某些任務是很是耗時的,例如IO設備(輸入輸出設備)、Ajax操做(從網絡讀取數據)、定時器...不得不等着結果出來,再往下執行。若是讓他們和別的任務同樣,都老老實實的排隊等待執行的話,執行效率會很是的低,甚至致使頁面的假死,用戶體驗不好。 函數
這個時候,任務隊列就派上用場了。oop
在JS中,全部任務能夠分紅兩種。一種是同步任務,另外一種是異步任務。 spa
同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務;異步任務指的是,不進入主線程,而進入"任務隊列"的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行。 線程
任務隊列中的任務事件,通常有個共性就是存在"回調函數"。所謂"回調函數",就是那些會被主線程掛起來的代碼。異步任務必須指定回調函數,當主線程開始執行異步任務時,執行就是對應的回調函數。
值得一提的是,任務隊列不止一條。因爲異步任務有不少種,好比事件監聽類,定時器類,Ajax請求類...因此能夠有不少條任務隊列
這樣說你們可能還不太明白,我畫個圖解釋下
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,因此整個的這種運行機制又稱爲Event Loop(事件輪詢)。
執行流程
(1)全部同步任務都在主線程上執行,造成一個執行棧(每執行一條代碼,向棧中壓入這條代碼)。 (2)主線程以外,還存在一個"任務隊列"。存放異步執行的代碼,如定時器、事件監聽回調函數等,進入等待狀態。 (3)一旦主線程中的全部同步任務執行完畢,就會讀取"任務隊列",看看裏面有哪些任務。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。 (4)主線程不斷重複上面的第三步(輪詢)。
具體舉個例子吧
假如咱們有一段代碼
var a = 11111 console.log(a) var btn1 = document.getElementById('btn1') btn1.onclick = function() { console.log(22222) } var btn2 = document.getElementById('btn2') btn2.onclick = function() { console.log(33333) } setTimeout(function() { console.log(44444) }, 1000) console.log(55555)
以上代碼在JS引擎中實際上是這樣執行的
var a = 11111 console.log(a) var btn1 = document.getElementById('btn1') var btn2 = document.getElementById('btn2') console.log(55555)
這五句代碼是同步代碼,會直接進入主線程,依次執行
btn.onclick = function() { console.log(22222) } btn2.onclick = function() { console.log(33333) } setTimeout(function() { console.log(44444) }, 1000)
這三塊異步代碼不會直接進入主線程,而是先在相應的任務隊列中註冊
當主線程執行完全部同步代碼時,就開始不斷輪詢任務隊列是否有任務須要執行,輪詢的過程很快。在輪詢過程當中,要是用戶點擊了btn1按鈕,任務隊列會通知主線程,"說我這有異步代碼已就緒,須要你來執行"。這時btn1.onclick就從任務隊列中彈出,到主線程中執行
一樣的,當過了1s時,任務隊列會通知定時器須要執行,這時主線程輪詢時獲得這條"通知",因此就執行定時器中語句
知道這個機制後,咱們再回頭看看那個面試題
setTimeout(function() { console.log(111); }, 0); // 這裏定時器時間設置爲0ms後執行 console.log(222);
這裏的console.log(222)
首先在主線程中執行,而定時器則是先在任務隊列中註冊。當主線程中代碼執行完(也就是console.log('222')
這條語句執行完後),主線程開始輪詢任務隊列中的異步代碼,因爲定時器設置的時間是0ms,因此任務隊列會當即通知主線程,能夠執行。最後定時器就會到主線程中開始執行。這就是爲何打印的結果先是222,後111。
JS的事件輪詢的機制,使任務隊列、JS主線程、異步操做之間能夠相互協做。這正是JS語言不同凡響的運行方式,也所以使它具有了其餘語言不具有的優點。 最後感謝你們百忙之中辛苦觀看,也但願這篇文章能夠幫助屏幕前的你更好的理解JS的Event Loop機制!