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