【W(wǎng)eb技術】1055- 如何理解單線程的JavaScript及其工作原理

隨著JavaScript的日益流行,團隊也利用其在前端,后端,混合APPS,嵌入式設備以及更多設備等開發(fā)棧中諸多層面的支持,JavaScript能創(chuàng)造出令人驚嘆的軟件,開發(fā)就必須更加深入了解JavaScript語言的內部工作機制!
此篇文章會詳細地研究并解釋JavaScript的工作原理,通過了解這些細節(jié),通過合理地使用提供的APIs,你將能夠寫出更好的,非阻塞的程序。
一、單線程的來源
幾乎所有人都已經(jīng)聽過V8引擎的概念,并且很多人知道JavaScript是單線程的,JavaScript作為瀏覽器腳本語言,它的主要用途是與用戶交互,那么為什么JavaScript是單線程呢?如果JavaScript是多線程,當頁面更新內容的時候、用戶又觸發(fā)了交互,這時候線程間的同步問題會變得很復雜,為了避免復雜性,故JavaScript被設計成單線程。
二、JavaScript引擎
谷歌V8引擎是一個流行的JavaScript引擎。這里有一個簡單的視圖來描繪其大概模樣。

引擎包括兩個組件:
內存堆——進行內存分配的區(qū)域 調用棧——代碼執(zhí)行時棧中的位置
三、運行時
幾乎每個JavaScript開發(fā)者都使用過一些瀏覽器 API(比如setTimeout)。然而這些API并不是引擎所提供的。這些Web API是瀏覽器提供的,還有DOM事件,AJAX及其它。它們怎么執(zhí)行呢,實際情況有點復雜。
于是乎,就有了如此流行的事件循環(huán)(也稱event loop或事件輪詢)和回調隊列。
四、調用棧
前面我們說過,JavaScript是單線程的編程語言,這意味著只有一個調用棧。這樣它只能一次做一件事情。調用棧是一種數(shù)據(jù)結構。當執(zhí)行進入一個函數(shù),把它置于棧的底部。如果從函數(shù)中返回則從棧底部移除函數(shù)。這就是調用棧所做的事情。
舉個??子,看如下代碼:
fucntion multiply(x,y) {
return x * y;
}
function printSquare(x) {
var s = mulitiply(x,x);
console.log(s);
}
printSquare(s);
當引擎開始執(zhí)行這段代碼的時候,調用棧會被清空,之后產(chǎn)生如下步驟:

五、堆棧溢出
一旦達到最大調用棧大小的時候發(fā)生。這種情況相當容易發(fā)生。特別是當你使用遞歸時,看下面的代碼:
function foo () {
foo();
}
foo();
當引擎開始執(zhí)行這段代碼的時候,它開始調用foo函數(shù)。這個函數(shù),然而會遞歸并開始調用其自身而沒有任何結束條件。所以在每步執(zhí)行過程中,調用堆棧會反復添加foo函數(shù)。就會出現(xiàn)如下情況:
當調用棧中的函數(shù)調用次數(shù)超過了調用棧的實際大小,瀏覽器決定拋出一個錯誤,如下圖所示:
小伙伴們可以自己在瀏覽器的控制臺里試試!
六、事件循環(huán)(Event Loop)
又該拿出那張經(jīng)典的圖了~

Event Loop 負責執(zhí)行代碼、收集和處理事件以及執(zhí)行隊列中的子任務。
具體包括:
Javascript 有一個主線程和執(zhí)行棧,所有的任務都會被放到調用棧等待主線程執(zhí)行 同步任務會被放在調用棧中,按照順序等待主線程依次執(zhí)行 主線程之外存在一個任務隊列,所有任務在主線程中以執(zhí)行棧的方式運行 同步任務都在主線程上執(zhí)行,棧中代碼在執(zhí)行的時候會調用 Web API,此時會產(chǎn)生一些異步任務 異步任務會在有了結果(比如被監(jiān)聽的事件發(fā)生時)后,將注冊的回調函數(shù)放入任務隊列中 執(zhí)行棧中任務執(zhí)行完畢后,此時主線程處于空閑狀態(tài),會從任務隊列中獲取任務進行處理
以上過程會不斷重復,這就是瀏覽器的運行機制,也是Event Loop。
最后
Event Loop 我們還得繼續(xù),理解它是突破前端第一層天花板的畢竟之路!
關于本文
來源:_啊嗚
https://juejin.cn/post/6991241111167565860

回復“加群”與大佬們一起交流學習~
點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章
