JavaScript的單線程性質以及定時器的工作原理

來源:互聯網
上載者:User

最近在寫JavaScript時遇到一些問題,就是當JavaScript多事件連續觸發,JavaScript的單線程引擎是如何控制的。找了一些資料,覺得很有用,在此分享一下。 

雖然不是原創,但是覺得此文章對JavaScript程式員非常有用。翻譯的不是十分精確,但希望對大家有用。

原文:John Resig   http://ejohn.org/blog/how-javascript-timers-work/

How JavaScript Timers Work

 

從基礎的層面來講,理解JavaScript的定時器是如何工作的是非常重要的。計時器的執行常常和我們的直觀想象不同,那是因為JavaScript引擎是單線程的。我們先來認識一下下面三個函數是如何控制計時器的。

  • var id = setTimeout(fn, delay); - 初始化一個計時器,然後在指定的時間間隔後執行。該函數返回一個唯一的標誌ID(Number類型),我們可以使用它來取消計時器。
  • var id = setInterval(fn, delay); - 和setTimeout有些類似,但它是連續調用一個函數(時間間隔是delay參數)直到它被取消。
  • clearInterval(id);, clearTimeout(id); - 使用計時器ID(setTimeout 和 setInterval的傳回值)來取消計時器回調的發生

為了理解計時器的內在執行原理,有一個重要的概念需要加以探討:計時器的延遲(delay)是無法得到保障的。由於所有JavaScript代碼是在一個線程裡執行的,所有非同步事件(例如,滑鼠點擊和計時器)只有擁有執行機會時才會執行。用一個很好的圖表加以說明:

在這個圖表中有許多資訊需要理解,如果完全理解了它們,你會對JavaScript引擎如何?非同步事件有一個很好的認識。這是一個一維的表徵圖:垂直方向表示時間,藍色的區塊表示JavaScript代碼執行塊。例如第一個JavaScript代碼執行塊需要大約18ms,滑鼠點擊所觸發的代碼執行塊需要11ms,等等。

由於JavaScript引擎同一時間只執行一條代碼(這是由於JavaScript單線程的性質),所以每一個JavaScript代碼執行塊會 “阻塞”其它非同步事件的執行。這就意味著當一個非同步事件發生(例如,滑鼠點擊,計時器被觸發,或者Ajax非同步請求)後,這些事件的回呼函數將排在執行隊列的最後等待執行(實際上,排隊的方式根據瀏覽器的不同而不同,所以這裡只是一個簡化);

從第一個JavaScript執行塊開始研究,在第一個執行塊中兩個計時器被初始化:一個10ms的setTimeout()和一個10ms的setInterval()。依據何時何地計時器被初始化(計時器初始化完畢後就會開始計時),計時器實際上會在第一個代碼塊執行完畢前被觸發。但是,計時器上綁定的函數不會立即執行(不被立即執行的原因是JavaScript是單線程的)。實際上,被延遲的函數將依次排在執行隊列的最後,等待下一次恰當的時間再執行。

此外,在第一個JavaScript執行塊中我們看到了一個“滑鼠點擊”事件發生了。一個JavaScript回呼函數綁定在這個非同步事件上了(我們從來不知道使用者什麼時候執行這個(點擊)事件,因此認為它是非同步),這個函數不會被立即執行,和上面的計時器一樣,它將排在執行隊列的最後,等待下一次恰當的時候執行。

當第一個JavaScript執行塊執行完畢後,瀏覽器會立即問一個問題:哪個函數(語句)在等待被執行?在這時,一個“滑鼠點擊事件處理函數”和一個“計時器回呼函數”都在等待執行。瀏覽器會選擇一個(實際上選擇了“滑鼠點擊事件的處理函數”,因為由圖可知它是先進隊的)立即執行。而“計時器回呼函數”將等待下次適合的時間執行。

注意,當“滑鼠點擊事件處理函數”執行的時候,setInterval的回呼函數第一次被觸發了。和setTimeout的回呼函數一樣,它將排到執行隊列的最後等待執行。但是,一定要注意這一點:當setInterval回呼函數第二次被觸發時(此時setTimeout函數仍在執行)setTimeout的第一次觸發將被拋棄掉。當一個很長的代碼塊在執行時,可能把所有的setInterval回呼函數都排在執行隊列的後面,代碼塊執行完之後,結果便會是一大串的setInterval回呼函數等待執行,並且這些函數之間沒有間隔,直到全部完成。所以,瀏覽器傾向於的當沒有更多interval的處理函數在排隊時再將下一個處理函數排到隊尾(這是由於間隔的問題)。

我們能夠發現,當第三個setInterval回呼函數被觸發時,之前的setInterval回呼函數仍在執行。這就說明了一個很重要的事實:setInterval不會考慮當前正在執行什麼,而把所有的堵塞的函數排到隊列尾部。這意味著兩次setInterval回呼函數之間的時間間隔會被犧牲掉(縮減)。

最後,當第二個setInterval回呼函數執行完畢後,我們可以看到沒有任何程式等待JavaScript引擎執行了。這就意味著瀏覽器現在在等待一個新的非同步事件的發生。在50ms時一個新的setInterval回呼函數再次被觸發,這時,沒有任何的執行塊阻塞它的執行了。所以它會立刻被執行。

讓我們用一個例子來闡明setTimeoutsetInterval之間的區別:

  setTimeout(function(){
    /* Some long block of code... */
    setTimeout(arguments.callee, 10);
  }, 10);
 
  setInterval(function(){
    /* Some long block of code... */
  }, 10); 

這兩句代碼乍一看沒什麼差別,但是它們是不同的。setTimeout回呼函數的執行和上一次執行之間的間隔至少有10ms(可能會更多,但不會少於10ms),而setInterval的回呼函數將嘗試每隔10ms執行一次,不論上次是否執行完畢。

在這裡我們學到了很多知識,總結一下:

  • JavaScript引擎是單線程的,強制所有的非同步事件排隊等待執行
  • setTimeoutsetInterval 在執行非同步代碼的時候有著根本的不同
  • 如果一個計時器被阻塞而不能立即執行,它將順延強制直到下一次可能執行的時間點才被執行(比期望的時間間隔要長些)
  • 如果setInterval回呼函數的執行時間將足夠長(比指定的時間間隔長),它們將連續執行並且彼此之間沒有時間間隔。

上述這些知識點都是非常重要的。瞭解了JavaScript引擎是如何工作的,尤其是大量的非同步事件(連續)發生時,才能為構建進階應用程式程式打好基礎。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.