摘要:此事件隊列的美妙之處在于它只是函數等待被調用和移動到調用棧的一個臨時存放區域。在事件循環不斷監視調用棧是否為空現在確實是空的時候調用創建一個新的調用棧來執行代碼。在執行完之后進入了一個新的狀態這個狀態調用棧為空事件記錄表為空事件隊列也為空。
這篇文章是對個人認為講解 JavaScript 事件循環比較清楚的一篇英文文章的簡單翻譯,原文地址是http://altitudelabs.com/blog/...。
介紹如果你像我一樣,喜歡JavaScript,是的,你肯定也會認同,JavaScript這門語言并不完美,嚴肅的說,沒有任何一門計算機語言是完美的。盡管JavaScript確實存在一些缺陷,但我喜歡編寫web程序以及如何用JavaScript構建能夠連接世界的應用。
JavaScript這門語言水很深,他復雜的內部原理需要花費一段時間才能夠真正的理解。其中的事件循環機制就不太好理解。很有可能一個多年使用JavaScript進行程序開發的人未必真正理解 JavaScript 的事件循環到底是怎么工作的。不管怎樣,通過本篇博客,我希望能夠揭示什么是事件循環以及能夠讓你覺得其實它真的沒那么復雜。
瀏覽器中的JavaScript當我們想到JavaScript時,我們通常會在Web瀏覽器的上下文中考慮它 - 這是有道理的,因為我們大多數情況下是在客戶端中(瀏覽器)運行JavaScript。然后,我們需要清楚的知道(因為這很重要),運行一個web應用,涉及到一系列的技術術語,如 JavaScript 引擎(像chrome V8) , 一系列的Web API(像DOM,BOM),還有事件循環和事件隊列。
當看到這么多術語,你可能會想,"我的天哪(食屎啦),看起來超級復雜。。。",你的想法有一定道理,但是你很快會看到,應用運行的基本原理其實并沒有那么復雜,雖然具體的底層實現超出了我們的范圍。
在我們深入到事件循環之前,我們需要理解下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! */
然后下面是調用棧中序列情況:
首先是Main.js 匿名主函數被調用:
secondFunction 方法被調用:
調用 secondFunction 后導致 firstFunction 被調用:
執行 firstFunction 在控制臺中打印了 "I"m first!",執行完后 firstFunction 中沒有更多的語句可以被執行了,所以 firstFunction 被移出了調用棧:
執行繼續,到 secondFunction 中,"I’m second!" 輸出到控制臺,同樣 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) 移動到事件記錄表中,并且按照五秒的時間延時進行注冊:
這是另一個“啊哈!”的時刻 - 注意一旦回調函數被移動到事件表,沒有任何東西(后面的代碼)被阻塞!程序繼續運行。
在幕后,事件表會時不時監視是否有事件發生從而觸發將對應的函數移動到事件隊列中等待被執行。在我們例子中,secondFunction 和 main.js 都完成了執行,調用棧為空。
在某一時刻,回調函數放在事件表中的時間將超過5秒。當發生這種情況時,事件表將firstFunction移動到事件隊列中。
在事件循環不斷監視調用棧是否為空,現在確實是空的時候,調用fistFunction,創建一個新的調用棧來執行代碼。
在執行完firstFunction之后,進入了一個新的狀態,這個狀態調用棧為空,事件記錄表為空,事件隊列也為空。監視程序一種保持運行,一旦事件隊列中存在待執行的函數,就會重復前面的步驟,執行函數,這就是事件循環。
總結我第一個承認我的解釋掩蓋了JavaScript引擎,事件表,事件隊列和事件循環底層的實際實現細節。 然而,對于我們絕大多數人來說,我們只需要對JavaScript執行異步功能時發生的情況有一個堅實的基礎理解就可以了。 并且,我希望上面的解釋能夠對你理解事件循環有幫助,這將是我們作為Web開發人員所必需要了解的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82820.html
摘要:而事件循環是主線程中執行棧里的代碼執行完畢之后,才開始執行的。由此產生的異步事件執行會作為任務隊列掛在當前循環的末尾執行。在下,觀察者基于監聽事件的完成情況在下基于多線程創建。 主要問題: 1、JS引擎是單線程,如何完成事件循環的? 2、定時器函數為什么計時不準確? 3、回調與異步,有什么聯系和不同? 4、ES6的事件循環有什么變化?Node中呢? 5、異步控制有什么難點?有什么解決方...
摘要:在這個視頻中,將的調用棧回調隊列和事件循環的內容講的很清晰。調用??梢酝锩娣艝|西,可以在事件結束的時候把回調函數放進回調隊列,然后是事件循環。為的時候這個過程看起來可能不明顯,除非考慮到調用棧的執行環境和事件循環的情況。 譯者按這篇文章可以看做是對Philip Roberts 2014年在JSConf演講的《What the heck is the event loop anyway...
摘要:定時器階段這個是事件循環開始的階段,綁定到這個階段的隊列,保留著定時器的回調,盡管它并沒有將回調推入隊列中,但是以最小的堆來維持計時器并且在到達規定的事件后執行回調。 本文,將會詳細的講解 node.js 事件循環工作流程和生命周期 一些常見的誤解 在 js 引擎內部的事件循環 最常見的誤解之一,事件循環是 Javascript 引擎(V8,spiderMonkey等)的一部分。事實上...
摘要:異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件循環隊列中。 基礎:瀏覽器 -- 多進程,每個tab頁獨立一個瀏覽器渲染進程(瀏覽器內核) 每個瀏覽器渲染進程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱為JS內核,負責處理Javascript腳本程序。(例如V8引擎) JS引擎線程負...
摘要:從異步過程的角度看,函數就是異步過程的發起函數,事件監聽函數就是異步過程的回調函數。事件觸發時,表示異步任務完成,會將事件監聽器函數封裝成一條消息放到消息隊列中,等待主線程執行。 1.為什么JavaScript是單線程? JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。JavaScrip...
閱讀 3947·2021-11-17 09:33
閱讀 3290·2021-10-08 10:05
閱讀 3119·2021-09-22 15:36
閱讀 1145·2021-09-06 15:02
閱讀 2776·2019-08-29 12:45
閱讀 1595·2019-08-26 13:40
閱讀 3406·2019-08-26 13:37
閱讀 428·2019-08-26 13:37