什麼是JavaScript 事件循環 ?

這篇文章是對我的認爲講解 JavaScript 事件循環比較清楚的一篇英文文章的簡單翻譯,原文地址是http://altitudelabs.com/blog/...javascript

介紹

  若是你像我同樣,喜歡JavaScript,是的,你確定也會認同,JavaScript這門語言並不完美,嚴肅的說,沒有任何一門計算機語言是完美的。儘管JavaScript確實存在一些缺陷,但我喜歡編寫web程序以及如何用JavaScript構建可以鏈接世界的應用。java

  JavaScript這門語言水很深,他複雜的內部原理須要花費一段時間纔可以真正的理解。其中的事件循環機制就不太好理解。頗有可能一個多年使用JavaScript進行程序開發的人未必真正理解 JavaScript 的事件循環究竟是怎麼工做的。無論怎樣,經過本篇博客,我但願可以揭示什麼是事件循環以及可以讓你以爲其實它真的沒那麼複雜。web

瀏覽器中的JavaScript

  當咱們想到JavaScript時,咱們一般會在Web瀏覽器的上下文中考慮它 - 這是有道理的,由於咱們大多數狀況下是在客戶端中(瀏覽器)運行JavaScript。而後,咱們須要清楚的知道(由於這很重要),運行一個web應用,涉及到一系列的技術術語,如 JavaScript 引擎(像chrome V8) , 一系列的Web API(像DOM,BOM),還有事件循環和事件隊列。chrome

  當看到這麼多術語,你可能會想,"個人天哪(食屎啦),看起來超級複雜。。。",你的想法有必定道理,可是你很快會看到,應用運行的基本原理其實並無那麼複雜,雖然具體的底層實現超出了咱們的範圍。瀏覽器

  在咱們深刻到事件循環以前,咱們須要理解下JavaScript引擎是幹什麼的?數據結構

JavaScript 引擎

  事實上,對於JavaScript引擎的實現有不少,可是目前爲止最知名的就是谷歌的Chrome 的 V8 引擎(V8 引擎不只僅只限存在於瀏覽器,它也存在於服務端,用於解析服務端的JavaScript 代碼,如NodeJS)。那麼,JavaScript 引擎到底作了些什麼呢? 其實很簡單,就是逐行逐句的處理JavaScript代碼,沒錯,一次只能處理一句,因此JavaScript是單線程的。這樣帶來的主要問題是若是你運行的JavaScript語句須要很長時間才能返回,則這個語句後面的全部代碼都會被阻塞。咱們固然不但願咱們寫的代碼會阻塞,特別是在瀏覽器端,能夠想象一下,若是你在一個網站上點擊一個按鈕,而後代碼就掛起了,你嘗試去單擊該網站頁面上的其餘按鈕,可是並無任何響應,會是怎麼一種體驗。這裏最可能的緣由是點擊按鈕觸發的代碼運行須要很長時間,使得後面的代碼被阻塞,致使整個網站UI沒法同時再響應用戶的交互事件。異步

  那麼 JavaScript 引擎是如何知道或者怎麼作到一次只執行一句JavaScript語句的呢? 答案是經過調用棧,能夠將調用棧想象成升降梯,第一我的進入升降梯將會在最後退出升降梯,然而最後一個進入的將會第一個出來。(做者在這裏的比喻彷佛不太好理解,可是你們確定都學過數據結構中的棧,其特色就是先進後出)。咱們看下下面的例子:函數

/* Within main.js */

var firstFunction = function () {  
  console.log("I'm first!");
};

var secondFunction = function () {  
  firstFunction();
  console.log("I'm second!");
};

secondFunction();

/* Results:
 * => I'm first!
 * => I'm second!
 */

而後下面是調用棧中序列狀況:oop

  • 首先是Main.js 匿名主函數被調用:網站

調用棧初始狀態

  • secondFunction 方法被調用:

secondFunction被調用

  • 調用 secondFunction 後致使 firstFunction 被調用:

firstFunction被調用

  • 執行 firstFunction 在控制檯中打印了 "I'm first!",執行完後 firstFunction 中沒有更多的語句能夠被執行了,因此 firstFunction 被移出了調用棧:

firstFunction返回

  • 執行繼續,到 secondFunction 中,"I’m second!" 輸出到控制檯,一樣 secondFunction 中沒有其餘更多的代碼要被執行了,因此也從調用棧中移出了。以此類推,最後調用棧會置空。

調用棧移出secondFunction

額,好的,可是咱們能來討論下事件循環嗎?

  如今咱們瞭解了JavaScript 引擎中的調用棧是怎麼工做的,咱們繼續回到剛纔說到代碼阻塞那裏,咱們知道咱們應該去避免它,可是應該怎麼作呢?幸運的是 JavaScript 提供了一種機制,它經過異步函數,不要擔憂,異步函數其實和其餘函數沒什麼區別,惟一區別是異步函數並不會當即立刻執行,會在後面某個時間點被觸發執行。若是你用過setTimeout函數,你已經對異步函數熟悉了。咱們來看下下面的例子:

/* Within main.js */

var firstFunction = function () {  
 console.log("I'm first!");
};

var secondFunction = function () {  
 setTimeout(firstFunction, 5000);
 console.log("I'm second!");
};

secondFunction();

/* Results:
 * => I'm second!
 * (And 5 seconds later)
 * => I'm first!
 */

一樣咱們接下來看下調用棧中序列狀況:

  • 在 secondFunction 執行到被放入調用棧以後,setTimeout 函數被調用,一樣也放入了調用棧。

異步函數

  • 在 setTimeout 函數執行以後,有個特別的地方,瀏覽器將 setTimeout 的回調函數(在上面例子中,firstFunction) 放在了一個能夠稱爲事件表(Event Table)的地方。 爲了便於理解,咱們能夠將這個事件表想象成註冊表:調用棧告訴事件表註冊特定的函數,只有當特定的事件發生了,這個函數才能被執行(應該是放入事件隊列)。而後當事件發生後,事件表就會簡單的將函數移動到事件隊列(Event Queue)中。此事件隊列的美妙之處在於,它只是函數等待被調用和移動到調用棧的一個臨時存放區域。

  • 你可能會問,"既然這樣,那麼事件隊列裏的這些函數何時會被移動到調用棧中執行?" 其實JavaScript引擎遵循着很是簡單的規則:底層會有程序時不時的檢查下調用棧是否爲空,無論何時一旦爲空,那麼該程序會檢查事件隊列裏是否會有正在等待被執行的函數。若是有,隊列中的第一個函數會被移動到調用棧中而後被執行。若是事件隊列爲空,這個監視程序將會一直保持運行,瞧! 我剛剛描述的就是臭名昭着的事件循環(Event Loop)!

  • 如今回到剛纔的例子,執行setTimeout 函數,將回調函數(例子中:firstFunction) 移動到事件記錄表中,而且按照五秒的時間延時進行註冊:

在 setTimeout 執行以後

  • 這是另外一個「啊哈!」的時刻 - 注意一旦回調函數被移動到事件表,沒有任何東西(後面的代碼)被阻塞!程序繼續運行。

在 secondFunction 執行以後

  • 在幕後,事件表會時不時監視是否有事件發生從而觸發將對應的函數移動到事件隊列中等待被執行。在咱們例子中,secondFunction 和 main.js 都完成了執行,調用棧爲空。

在 main 執行結束後

  • 在某一時刻,回調函數放在事件表中的時間將超過5秒。當發生這種狀況時,事件表將firstFunction移動到事件隊列中。

過了5秒後

  • 在事件循環不斷監視調用棧是否爲空,如今確實是空的時候,調用fistFunction,建立一個新的調用棧來執行代碼。

執行firstFunction

  • 在執行完firstFunction以後,進入了一個新的狀態,這個狀態調用棧爲空,事件記錄表爲空,事件隊列也爲空。監視程序一種保持運行,一旦事件隊列中存在待執行的函數,就會重複前面的步驟,執行函數,這就是事件循環。

此時狀態

總結

  我第一個認可個人解釋掩蓋了JavaScript引擎,事件表,事件隊列和事件循環底層的實際實現細節。 然而,對於咱們絕大多數人來講,咱們只須要對JavaScript執行異步功能時發生的狀況有一個堅實的基礎理解就能夠了。 而且,我但願上面的解釋可以對你理解事件循環有幫助,這將是咱們做爲Web開發人員所必須要了解的。

相關文章
相關標籤/搜索