事件循環是用來理解JavaScript的最重要的方面之一。這篇文章旨在解釋JavaScript如何與單個線程一起工作的細節,以及它如何處理異步函數。
JavaScript代碼運行是單線程。一次只執行一件事。這實際上是一個非常有用的限制,因為它簡化了很多程序,從而不必擔心并發問題。
您只需要注意編寫代碼的方式,避免任何可能阻塞線程的內容,如同步調用或無限循環。
通常,在大多數瀏覽器中,每個瀏覽器都有一個事件循環,以使每個進程隔離,并避免web頁面具有無限循環或繁重的處理來阻塞整個瀏覽器。
你最需要擔心的是,您的代碼將在單個事件循環上運行,并在編寫代碼時考慮到這一點,以避免阻塞它。
阻止事件循環
任何花費太長時間將控制權返回給事件循環的JavaScript代碼都會阻止頁面中任何JavaScript代碼的執行,甚至阻止UI線程,用戶也無法點擊,滾動頁面等等。
幾乎所有JavaScript中的I / O操作都是非阻塞的。網絡請求,Node.js文件系統操作等。阻塞是個例外,這就是為什么JavaScript基于回調,以及最近的promises和async / await。
調用堆棧
調用堆棧是LIFO隊列(Last In,FirstOut)。事件循環不斷檢查調用堆棧以查看是否存在需要運行的任何函數。
在執行此操作時,它會將它找到的任何函數調用添加到調用堆棧并按順序執行每個調用。
一個簡單的事件循環說明:
當此代碼運行時,首先foo()調用。在foo()我們第一次調用bar(),然后我們調用baz()。
排隊功能執行
上面的例子運行特點:JavaScript找到要執行的東西,按順序運行它們。
讓我們看看如何推遲函數直到堆棧清除:
用例setTimeout(()=> {}), 0)是調用一個函數,但是一旦執行了代碼中的每個其他函數就執行它。
當此代碼運行時,首先調用foo()。在foo()里面我們首先調用setTimeout,bar作為參數傳遞,然后我們指示它盡可能快地運行,將0作為計時器傳遞。然后我們調用baz()。
消息隊列
調用setTimeout()時,瀏覽器或Node.js啟動計時器。當計時器到期,我們將0作為超時,回調函數立即被放入消息隊列中。
消息隊列也是用戶發起的事件(如單擊事件、鍵盤事件或獲取響應)在代碼有機會對其作出響應之前排隊的地方。或者像onLoad這樣的DOM事件。
循環優先處理調用堆棧,它首先處理在調用堆棧中找到的所有東西,一旦調用堆棧中沒有任何東西,它就會去獲取事件隊列中的東西。
我們不必等待像setTimeout,fetch或其他東西這樣的函數來完成自己的工作,因為它們是由瀏覽器提供的,并且它們運行在自己的線程中。
ES6作業隊列
ECMAScript 2015引入了Promises使用的作業隊列概念(也在ES6 / ES2015中引入)。這是一種盡快執行異步函數結果的方法,而不是放在調用堆棧的末尾。
在當前函數結束之前解析的Prom將在當前函數之后立即執行。
我覺得在游樂園里過山車的比喻很好:消息隊列將你放在隊列的后面,在所有其他人的后面,你將不得不等待輪到你,而作業隊列是快速通票這可以讓你在完成上一個之后再騎一次。
這是Promises(和Async / await,它建立在promises上)和普通的舊異步函數setTimeout()或其他平臺API 之間的巨大差異。
javascrit的事件循環是這門語言中非常重要且基礎的概念。清楚的了解了事件循環的執行順序和每一個階段的特點,可以使我們對一段異步代碼的執行順序有一個清晰的認識,從而減少代碼運行的不確定性。合理的使用各種延遲事件的方法,有助于代碼更好的按照其優先級去執行。
-
Web
+關注
關注
2文章
1265瀏覽量
69531 -
javascript
+關注
關注
0文章
519瀏覽量
53889
發布評論請先 登錄
相關推薦
評論