從一個例子引發對JS運行機制之 Event Loop 的思考

來源:互聯網
上載者:User

標籤:article   延遲   使用   最小   解決   為什麼   targe   script   stack   

栗子如下:

for (var i = 0; i < 5; i++) {    setTimeout(function() {        console.log(‘i: ‘,i);        //一秒之後輸出幾乎沒有時間間隔依次輸出5個5    }, 1000);}console.log(i);                      //立即輸出5

想必很多人看到立馬能看出答案吧,但是為什麼定時器不能依次列印出1,2,3,4,5呢?答案稍後分曉。

那到底怎麼才能依次輸出我們想要的結果呢?大家可能都想到是利用閉包,或者是利ES6中的let聲明,但是今天我們不講這個。

一、為什麼js是單線程?

大家都知道js不同於其他語言,它是單線程的。那麼問題來了,為什麼不是多線程呢?按道理來說多線程不是能夠同時解決問題提高效率嗎?除了多線程產生衝突、搶佔資源等答案,還可以是什麼呢?

其實,這跟它作為瀏覽器指令碼語言的用途有關,瀏覽器的指令碼語言主要的用途是用來與使用者互動,會產生DOM的操作,這就是問題的關鍵,假設js是多線程,有一個線程是刪除DOM操作,有個在當前DOM新增內容,這時候瀏覽器應該怎麼辦呢?這就決定了js應該被設計成單線程。那js有沒有多線程的可能呢?答案是肯定的,HTML5提出的web Worker標準,但是,

“為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript指令碼建立多個線程,但是子線程完全受主線程式控制制,且不得操作DOM。所以,這個新標準並沒有改變JavaScript單線程的本質”  ----阮一峰的一篇部落格。

 

二 、 Event Loop 事件迴圈

因為js是單線程,所以執行時候需要排隊,前一個任務結束之後,後一個任務才會執行,那麼如果前一個任務執行很久,後面一個任務就要等很久。如果一些等待不是因為CPU計算慢產生的,比如IO裝置的使用,那麼js會把等待的任務掛起,執行後面的任務,等IO返回結果,再回頭執行直線掛起的任務。

這樣任務就有了同步任務和非同步任務,同步任務是主線程上執行的任務,形成一個執行棧(execution context stack),是排隊進行的。非同步任務不是在主線程執行的,是在“任務隊列”(Queue)裡面,只有當主線程所有同步的任務執行完成,任務隊列中的非同步任務才會進入主線程,然後被執行。

非同步執行機制如下:---阮一峰

 

可視化描述---MDN

主線程上:

一個函數1被調用了,建立一個堆疊框架,包含了函數1的參數和局部變數,當函數1中又調用了函數2,又建立了一個堆疊框架,此時這個堆疊框架在第一個堆疊框架之前,包含了函數2的參數和局部變數。主線程先執行了至於頂層的堆疊框架(函數2產生的),當函數2返回時,對應的堆疊框架就出棧了,接著繼續執行函數1的堆疊框架,直到棧空了。

訊息佇列:
一個 js運行時包含了一個待處理的訊息佇列。每一個訊息都與一個函數相關聯。當棧為空白時,從隊列中取出一個訊息進行處理。這個處理過程包含了調用與這個訊息相關聯的函數(以及因而建立了一個初始堆疊框架)。當棧再次為空白的時候,也就意味著訊息處理結束。 

添加訊息:

在瀏覽器裡,添加事件可以是當一個事件出現且有一個事件監聽器被綁定時,訊息被隨時添加。也可以是在調用setTimeout等函數時候,

在將來的某個時間後在訊息對列中添加。

setTimeout:

調用該函數時候會在將來的某個時間後在訊息對列中添加一個訊息,如果執行棧中有其他任務沒有完成(假設有一個很耗時的計算),setTimeout訊息必須必須等到執行棧的任務完成才會處理,所以說該函數的第二個參數僅僅表示最少的時間 而非確切的時間。

即使你設定零延遲:

 setTimeout(function cb1() {    console.log(‘我是第二個被執行的’);  }, 0);                            console.log(‘我是第一個被執行的’);       //先列印這句              

事實上,js中規定,定時器的第二個參數設定最少不能小於4ms, 小於的話就按最小的4ms執行。

 

中,主線程啟動並執行時候,產生堆(heap)和棧(stack),棧中的代碼調用各種外部API,它們在"任

務隊列"中加入各種事件(click,load)。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回呼函數。

 

三、回頭看看開頭的栗子

for迴圈和外面的console.log()是主線程上的同步任務,他們按循序執行,先執行for迴圈結束(此時i變為5),再執行console.log();

for迴圈中的setTimeout是在任務隊列中,它只有等在前主線程中的棧空了之後,到一個時間才會被被執行,此時範圍中的i已經變為5,此時setTimeout中的回呼函數所讀取的i就是5了。

還有一個問題?

輸出5的時間間隔是多少?答案顯而易見是,立即列印一個5,1000ms之後幾乎同時輸出5個5; why???

正如上面解釋的,for迴圈一次,會在任務隊列中加上一個setTimeou任務(該任務是在1000ms後執行回呼函數),這樣迴圈結束,工作清單裡面就有了5個setTimeou任務,且當主線程中棧空了之後,工作清單就開始進棧,等待1000ms之後執行回調,所以後面的5個5幾乎在一個點依次列印出來。

 

那麼js執行的順序就可以總結為:

主線程(同步任務) =》任務隊列(非同步) =》回調

 

從一個例子引發對JS運行機制之 Event Loop 的思考

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.