javascript中event loop是什麼
宣告
介紹
如何你跟我一樣的話,那麼你一定會愛上javascript!雖然它不是一種比較完美的程式語言,但是嚴格地說,還有其它比javascript更完美的語言來設計web嗎?所以請忽視javascript的缺陷吧。我喜歡web工作,而javascript使得我可以用其來建立applications,從而跟web上的使用者聯絡起來!
但是如果你深究javascript——你會發現,它的一些內部概念你需要花費許多時間才能真正理解!其中一個概念就是Event Loop,可能一個在web開發中工作了幾年的開發人員也沒有真正理解其含義和工作原理,不管怎樣,我希望通過這篇文章,能帶給你一些啟發:什麼是event loop?其實它並不是那麼難以理解的!
瀏覽器中的Javascript
我們通常所說的javascript,都是指瀏覽器端使用的javascript——因為我們寫的javascript程式碼大多數是用在客戶端的!不管怎樣,理解這些概念和技術是非常重要的:1、Javascript Engine(比如 Chrome's V8),2、Web API(如 DOM),3、Event Loop 和 Event Quene。
你可能看過上面列舉的這些概念之後,你可能會這樣想:“好複雜呀!”,確實是這樣的!但是通過這篇文章待會兒你會發現,其實它們並沒有那麼複雜!
在介紹event loop之前,我們需要對javascript engine做一個簡單的理解來明白它是做什麼的!
Javascript Engine
有幾種不同的js引擎,但是最流行的一個當屬谷歌的V8了(它並不僅僅在瀏覽器環境中使用,通過nodejs,它也在伺服器端使用)。但是js引擎的工作到底是什麼呢?其實,引擎的工作非常簡單——它會遍歷我們寫的所有js程式碼,然後一個一個的執行它們。也就是說我們的js是單執行緒的,單執行緒不好的地方就是,如果你同步執行了一段很長時間的程式碼的話,那麼這段程式碼後面的邏輯會被阻塞從而等待很久才能執行。同門通常都不想寫阻塞的程式碼——特別是在瀏覽器中,設想如果你點選了一個按鈕之後去執行了一個很長時間的阻塞程式碼,這會導致你的頁面的其它互動效果會暫停,嚴重影響使用者體驗!
那麼js引擎是怎麼知道一個一個的處理程式碼呢?其實它使用了call stack(呼叫棧)。你可以把呼叫棧看作一個電梯——第一個進電梯的人最後一個出來,最後一個進電梯的人第一個出來!
我們來看一個例子:
/*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!
*/複製程式碼
首先,js引擎會維護一個呼叫棧序列的:
- main.js第一次執行的時候,呼叫棧序列是這樣的:
- 當呼叫secondFunction的時候,呼叫棧序列:
- 我們呼叫secondFunction導致firstFunction被呼叫,所以此時序列是這樣的:
- 當執行完firstFunction,會列印“I'm first!”,然後在其內部就沒有其它程式碼執行了,所以呼叫棧序列需要把firstFunction移除,所以此時序列是這樣的:
- 同樣,secondFunction列印完“I'm second!”之後,在其內部也沒有其它程式碼需要執行了,所以會移除其,所以此時序列是這樣的:
- 最後,當main.js中沒有其它程式碼需要執行的時候,那麼匿名函式也會被從呼叫棧中移除,從而這樣:
好了,說了半天,什麼是Event Loop?
到現在,我們已經知道js引擎的呼叫棧的原理了,那麼我們回到剛才所討論的阻塞程式碼的問題上,我們都知道應該避免寫阻塞程式碼!慶幸的是,js提供了一種機制來實現非阻塞:非同步回撥函式!我們又接觸了一個概念,是不是以為這個概念很牛逼呢?其實並不是,非同步回撥函式跟你寫過的其它的普通的函式一樣的!只不過它不是同步執行的,如果你熟悉的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 Table理解成為一個序號產生器構,呼叫棧會告訴Event Table去註冊一個特別的函式,當特定的事件發生的時候去執行它,當特定的事件發生時,Event Table只是簡單的把這個函式移動到Event Quene裡面,Event Quene只是一個放置函式即將執行的暫存區,最後由Event Quene把函式移動到呼叫棧中。
你可能會問,Event Quene什麼時候知道把回撥函式放回到呼叫棧中呢?好,js引擎遵從一個簡單的規則:瀏覽器中存在一個程式會不斷地去檢查呼叫棧是否為空,同時,無論任何時候只要呼叫棧為空,它會檢查Event Quene是否有待執行的函式佇列!如果呼叫棧為空,那麼它會把Event Quene佇列裡面的第一個函式放到呼叫棧中,如果Event Quene為空的話,那麼這個程式將會無限期地來回地進行檢查,好吧——我們描述的這個就是所謂的Event Loop!
- 現在回到我們的例子,執行的setTimeout函式的時候,我們向Event Table裡面註冊了一個回撥函式(在這個例子中是firstFunction),並且延遲5秒可執行!呼叫棧序列此時是這樣的:
- 我們執行程式碼的時候會發現,我們的程式碼並沒有等待5秒之後才列印“I'm second!”,而是自然地執行了下一行程式碼,此時程式碼序列是這樣的:
- 在背後,Event Table會不斷的監視跟回撥函式相關的時候那個事件(這個例子中為等待5秒)是否發生,從而把回撥函式移動到可執行的Event Quene序列裡面。與此同時,secondFunction和main.js中匿名函式都執行完了,所以呼叫棧序列此時是這樣的:
- 經過5秒之後,event table會移動firstFunction到event quene裡面:
- 同時event loop不斷的監視當前呼叫棧是否為空,如果是,則把event quene序列裡面的第一個回撥函式放到呼叫棧(新的,不同於剛才的呼叫棧,剛才的已經沒有了)裡面。所以此時呼叫棧序列是這樣的:
- 當firstFunction執行完成之後,瀏覽器會返回一個狀態:呼叫棧會空,event table沒有任何事件可監聽,同時Event Quene佇列為空!最後是這樣的: