摘要:此事件隊列的美妙之處在于它只是函數(shù)等待被調(diào)用和移動到調(diào)用棧的一個臨時存放區(qū)域。在事件循環(huán)不斷監(jiān)視調(diào)用棧是否為空現(xiàn)在確實是空的時候調(diào)用創(chuàng)建一個新的調(diào)用棧來執(zhí)行代碼。在執(zhí)行完之后進入了一個新的狀態(tài)這個狀態(tài)調(diào)用棧為空事件記錄表為空事件隊列也為空。
這篇文章是對個人認為講解 JavaScript 事件循環(huán)比較清楚的一篇英文文章的簡單翻譯,原文地址是http://altitudelabs.com/blog/...。
介紹如果你像我一樣,喜歡JavaScript,是的,你肯定也會認同,JavaScript這門語言并不完美,嚴肅的說,沒有任何一門計算機語言是完美的。盡管JavaScript確實存在一些缺陷,但我喜歡編寫web程序以及如何用JavaScript構(gòu)建能夠連接世界的應(yīng)用。
JavaScript這門語言水很深,他復(fù)雜的內(nèi)部原理需要花費一段時間才能夠真正的理解。其中的事件循環(huán)機制就不太好理解。很有可能一個多年使用JavaScript進行程序開發(fā)的人未必真正理解 JavaScript 的事件循環(huán)到底是怎么工作的。不管怎樣,通過本篇博客,我希望能夠揭示什么是事件循環(huán)以及能夠讓你覺得其實它真的沒那么復(fù)雜。
瀏覽器中的JavaScript當(dāng)我們想到JavaScript時,我們通常會在Web瀏覽器的上下文中考慮它 - 這是有道理的,因為我們大多數(shù)情況下是在客戶端中(瀏覽器)運行JavaScript。然后,我們需要清楚的知道(因為這很重要),運行一個web應(yīng)用,涉及到一系列的技術(shù)術(shù)語,如 JavaScript 引擎(像chrome V8) , 一系列的Web API(像DOM,BOM),還有事件循環(huán)和事件隊列。
當(dāng)看到這么多術(shù)語,你可能會想,"我的天哪(食屎啦),看起來超級復(fù)雜。。。",你的想法有一定道理,但是你很快會看到,應(yīng)用運行的基本原理其實并沒有那么復(fù)雜,雖然具體的底層實現(xiàn)超出了我們的范圍。
在我們深入到事件循環(huán)之前,我們需要理解下JavaScript引擎是干什么的?
JavaScript 引擎事實上,對于JavaScript引擎的實現(xiàn)有很多,但是目前為止最知名的就是谷歌的Chrome 的 V8 引擎(V8 引擎不僅僅只限存在于瀏覽器,它也存在于服務(wù)端,用于解析服務(wù)端的JavaScript 代碼,如NodeJS)。那么,JavaScript 引擎到底做了些什么呢? 其實很簡單,就是逐行逐句的處理JavaScript代碼,沒錯,一次只能處理一句,所以JavaScript是單線程的。這樣帶來的主要問題是如果你運行的JavaScript語句需要很長時間才能返回,則這個語句后面的所有代碼都會被阻塞。我們當(dāng)然不希望我們寫的代碼會阻塞,特別是在瀏覽器端,可以想象一下,如果你在一個網(wǎng)站上點擊一個按鈕,然后代碼就掛起了,你嘗試去單擊該網(wǎng)站頁面上的其他按鈕,但是并沒有任何響應(yīng),會是怎么一種體驗。這里最可能的原因是點擊按鈕觸發(fā)的代碼運行需要很長時間,使得后面的代碼被阻塞,導(dǎo)致整個網(wǎng)站UI無法同時再響應(yīng)用戶的交互事件。
那么 JavaScript 引擎是如何知道或者怎么做到一次只執(zhí)行一句JavaScript語句的呢? 答案是通過調(diào)用棧,可以將調(diào)用棧想象成升降梯,第一個人進入升降梯將會在最后退出升降梯,然而最后一個進入的將會第一個出來。(作者在這里的比喻似乎不太好理解,但是大家肯定都學(xué)過數(shù)據(jù)結(jié)構(gòu)中的棧,其特點就是先進后出)。我們看下下面的例子:
/* 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! */
然后下面是調(diào)用棧中序列情況:
首先是Main.js 匿名主函數(shù)被調(diào)用:
secondFunction 方法被調(diào)用:
調(diào)用 secondFunction 后導(dǎo)致 firstFunction 被調(diào)用:
執(zhí)行 firstFunction 在控制臺中打印了 "I"m first!",執(zhí)行完后 firstFunction 中沒有更多的語句可以被執(zhí)行了,所以 firstFunction 被移出了調(diào)用棧:
執(zhí)行繼續(xù),到 secondFunction 中,"I’m second!" 輸出到控制臺,同樣 secondFunction 中沒有其他更多的代碼要被執(zhí)行了,所以也從調(diào)用棧中移出了。以此類推,最后調(diào)用棧會置空。
額,好的,但是我們能來討論下事件循環(huán)嗎?現(xiàn)在我們了解了JavaScript 引擎中的調(diào)用棧是怎么工作的,我們繼續(xù)回到剛才說到代碼阻塞那里,我們知道我們應(yīng)該去避免它,但是應(yīng)該怎么做呢?幸運的是 JavaScript 提供了一種機制,它通過異步函數(shù),不要擔(dān)心,異步函數(shù)其實和其他函數(shù)沒什么區(qū)別,唯一區(qū)別是異步函數(shù)并不會立即馬上執(zhí)行,會在后面某個時間點被觸發(fā)執(zhí)行。如果你用過setTimeout函數(shù),你已經(jīng)對異步函數(shù)熟悉了。我們來看下下面的例子:
/* 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! */
同樣我們接下來看下調(diào)用棧中序列情況:
在 secondFunction 執(zhí)行到被放入調(diào)用棧之后,setTimeout 函數(shù)被調(diào)用,同樣也放入了調(diào)用棧。
在 setTimeout 函數(shù)執(zhí)行之后,有個特別的地方,瀏覽器將 setTimeout 的回調(diào)函數(shù)(在上面例子中,firstFunction) 放在了一個可以稱為事件表(Event Table)的地方。 為了便于理解,我們可以將這個事件表想象成注冊表:調(diào)用棧告訴事件表注冊特定的函數(shù),只有當(dāng)特定的事件發(fā)生了,這個函數(shù)才能被執(zhí)行(應(yīng)該是放入事件隊列)。然后當(dāng)事件發(fā)生后,事件表就會簡單的將函數(shù)移動到事件隊列(Event Queue)中。此事件隊列的美妙之處在于,它只是函數(shù)等待被調(diào)用和移動到調(diào)用棧的一個臨時存放區(qū)域。
你可能會問,"既然這樣,那么事件隊列里的這些函數(shù)什么時候會被移動到調(diào)用棧中執(zhí)行?" 其實JavaScript引擎遵循著非常簡單的規(guī)則:底層會有程序時不時的檢查下調(diào)用棧是否為空,不管什么時候一旦為空,那么該程序會檢查事件隊列里是否會有正在等待被執(zhí)行的函數(shù)。如果有,隊列中的第一個函數(shù)會被移動到調(diào)用棧中然后被執(zhí)行。如果事件隊列為空,這個監(jiān)視程序?qū)恢北3诌\行,瞧! 我剛剛描述的就是臭名昭著的事件循環(huán)(Event Loop)!
現(xiàn)在回到剛才的例子,執(zhí)行setTimeout 函數(shù),將回調(diào)函數(shù)(例子中:firstFunction) 移動到事件記錄表中,并且按照五秒的時間延時進行注冊:
這是另一個“啊哈!”的時刻 - 注意一旦回調(diào)函數(shù)被移動到事件表,沒有任何東西(后面的代碼)被阻塞!程序繼續(xù)運行。
在幕后,事件表會時不時監(jiān)視是否有事件發(fā)生從而觸發(fā)將對應(yīng)的函數(shù)移動到事件隊列中等待被執(zhí)行。在我們例子中,secondFunction 和 main.js 都完成了執(zhí)行,調(diào)用棧為空。
在某一時刻,回調(diào)函數(shù)放在事件表中的時間將超過5秒。當(dāng)發(fā)生這種情況時,事件表將firstFunction移動到事件隊列中。
在事件循環(huán)不斷監(jiān)視調(diào)用棧是否為空,現(xiàn)在確實是空的時候,調(diào)用fistFunction,創(chuàng)建一個新的調(diào)用棧來執(zhí)行代碼。
在執(zhí)行完firstFunction之后,進入了一個新的狀態(tài),這個狀態(tài)調(diào)用棧為空,事件記錄表為空,事件隊列也為空。監(jiān)視程序一種保持運行,一旦事件隊列中存在待執(zhí)行的函數(shù),就會重復(fù)前面的步驟,執(zhí)行函數(shù),這就是事件循環(huán)。
總結(jié)我第一個承認我的解釋掩蓋了JavaScript引擎,事件表,事件隊列和事件循環(huán)底層的實際實現(xiàn)細節(jié)。 然而,對于我們絕大多數(shù)人來說,我們只需要對JavaScript執(zhí)行異步功能時發(fā)生的情況有一個堅實的基礎(chǔ)理解就可以了。 并且,我希望上面的解釋能夠?qū)δ憷斫馐录h(huán)有幫助,這將是我們作為Web開發(fā)人員所必需要了解的。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82820.html
摘要:而事件循環(huán)是主線程中執(zhí)行棧里的代碼執(zhí)行完畢之后,才開始執(zhí)行的。由此產(chǎn)生的異步事件執(zhí)行會作為任務(wù)隊列掛在當(dāng)前循環(huán)的末尾執(zhí)行。在下,觀察者基于監(jiān)聽事件的完成情況在下基于多線程創(chuàng)建。 主要問題: 1、JS引擎是單線程,如何完成事件循環(huán)的? 2、定時器函數(shù)為什么計時不準確? 3、回調(diào)與異步,有什么聯(lián)系和不同? 4、ES6的事件循環(huán)有什么變化?Node中呢? 5、異步控制有什么難點?有什么解決方...
摘要:在這個視頻中,將的調(diào)用?;卣{(diào)隊列和事件循環(huán)的內(nèi)容講的很清晰。調(diào)用棧可以往里面放東西,可以在事件結(jié)束的時候把回調(diào)函數(shù)放進回調(diào)隊列,然后是事件循環(huán)。為的時候這個過程看起來可能不明顯,除非考慮到調(diào)用棧的執(zhí)行環(huán)境和事件循環(huán)的情況。 譯者按這篇文章可以看做是對Philip Roberts 2014年在JSConf演講的《What the heck is the event loop anyway...
摘要:定時器階段這個是事件循環(huán)開始的階段,綁定到這個階段的隊列,保留著定時器的回調(diào),盡管它并沒有將回調(diào)推入隊列中,但是以最小的堆來維持計時器并且在到達規(guī)定的事件后執(zhí)行回調(diào)。 本文,將會詳細的講解 node.js 事件循環(huán)工作流程和生命周期 一些常見的誤解 在 js 引擎內(nèi)部的事件循環(huán) 最常見的誤解之一,事件循環(huán)是 Javascript 引擎(V8,spiderMonkey等)的一部分。事實上...
摘要:異步請求線程在在連接后是通過瀏覽器新開一個線程請求將檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個回調(diào)再放入事件循環(huán)隊列中。 基礎(chǔ):瀏覽器 -- 多進程,每個tab頁獨立一個瀏覽器渲染進程(瀏覽器內(nèi)核) 每個瀏覽器渲染進程是多線程的,主要包括:GUI渲染線程 JS引擎線程 也稱為JS內(nèi)核,負責(zé)處理Javascript腳本程序。(例如V8引擎) JS引擎線程負...
摘要:從異步過程的角度看,函數(shù)就是異步過程的發(fā)起函數(shù),事件監(jiān)聽函數(shù)就是異步過程的回調(diào)函數(shù)。事件觸發(fā)時,表示異步任務(wù)完成,會將事件監(jiān)聽器函數(shù)封裝成一條消息放到消息隊列中,等待主線程執(zhí)行。 1.為什么JavaScript是單線程? JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。JavaScrip...
閱讀 4020·2021-11-17 09:33
閱讀 3311·2021-10-08 10:05
閱讀 3140·2021-09-22 15:36
閱讀 1179·2021-09-06 15:02
閱讀 2799·2019-08-29 12:45
閱讀 1624·2019-08-26 13:40
閱讀 3439·2019-08-26 13:37
閱讀 453·2019-08-26 13:37