Javascript定時器(一)——單線程

可以從下面的代碼中看到,第一個用setTimeout中的代碼是死循環,由於是單線程,下面的兩個定時器就沒機會執行了。

<script type="text/javascript">
    setTimeout( function(){ while(true){} } , 100); 
    setTimeout( function(){ alert('你好!setTimeout'); } , 200); 
    setInterval( function(){ alert('你好!setInterval'); }  , 200); 
</script>

瀏覽器的內核是多線程的,它們在內核制控下相互配合以保持同步,一個瀏覽器至少實現3個常駐線程:javascript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。

  1. JavaScript引擎是基於事件驅動單線程執行的,JS引擎一直等待着任務隊列中任務的到來然後加以處理,瀏覽器無論再什麼時候都只有一個JS線程在運行JS程序。
  2. GUI渲染線程負責渲染瀏覽器界面,當界面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該線程就會執行。但需要注意 GUI渲染線程與JS引擎是互斥的,當JS引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JS引擎空閒時立即被執行。
  3. 瀏覽器事件觸發線程,當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeOut、也可來自瀏覽器內核的其他線程如鼠標點擊、AJAX異步請求等,但由於JS的單線程關係所有這些事件都得排隊等待JS引擎處理

  由上圖可看出,瀏覽器中的JavaScript引擎是基於事件驅動的,這裏的事件可看作是瀏覽器派給它的各種任務,JavaScript引擎一直等待着任務隊列中任務的到來,由於單線程關係,這些任務得進行排隊,一個接着一個被引擎處理。

t1、t2....tn表示不同的時間點,tn下面對應的小方塊代表該時間點的任務。

t1時刻:

  1. GUI渲染線程
  2. 瀏覽器事件觸發線程:

    在t1時間段內,首先是用戶點擊了一個鼠標鍵,點擊被瀏覽器事件觸發線程捕捉後形成一個鼠標點擊事件,由圖可知,對於JavaScript引擎線程來說,這事件是由其它線程異步傳到任務隊列尾的,由於引擎正在處理t1時的任務,這個鼠標點擊事件正在等待處理。
  3. 定時觸發線程:

    這裏的瀏覽器模型定時計數器並不是由JavaScript引擎計數的,因爲JavaScript引擎是單線程的,如果處於阻塞線程狀態就計不了時,它必須依賴外部來計時並觸發定時,所以隊列中的定時事件是異步事件。
  4. 在這t1的時間段內,繼鼠標點擊事件觸發後,先前已設置的setTimeout定時也到達了,此刻對JavaScript引擎來說,定時觸發線程產生了一個異步定時事件並放到任務隊列中,該事件被排到點擊事件回調之後,等待處理。同理,還是在t1時間段內,接下來某個setInterval定時器也被添加了,由於是間隔定時,在t1段內連續被觸發了兩次,這兩個事件被排到隊尾等待處理。

  5. ajax異步請求:

    瀏覽器新開一個http線程請求,當請求的狀態變更時,如果先前已設置回調,這異步線程就產生狀態變更事件放到JavaScript引擎的處理隊列中等待處理。

二、任務的執行順序不同,顯示結果也不同

1)未使用setTimeout函數

在網上找到的一段代碼實例,這裏用來演示一下。

複製代碼
<a href="#" id="doBtn">do something</a>
<div id="status"></div>
<script type="text/javascript">
      var doBtn = document.getElementById('doBtn')
          , status = document.getElementById('status');

      function sleep(ms) {
        var start = new Date();
        while (new Date() - start <= ms) {}
      }
      
      doBtn.onclick = function(e) {
          status.innerHTML = 'doing...please wait...';  
          sleep(3000);  // 模擬一個耗時較長的計算過程,3s
          status.innerHTML = 'done';  
          return false;
      };
</script>
複製代碼

我在firefox中執行了上面的代碼。計劃是點擊“do something”按鈕,然後顯示“doing...please wait...”,接着執行sleep,最後顯示“done”。

但是結果是點擊後,瀏覽器卡住3秒左右,最後直接顯示done。

分析下看出,在做status.innerHTML設置的時候,是需要執行GUI渲染線程的,但是現在還在執行JavaScript引擎線程,而JavaScript引擎線程與GUI渲染線程是互斥的,所以就最後顯示了done。

 

2)使用了setTimeout函數

複製代碼
<a href="#" id="doBtn2">do something timer</a>
<div id="status2"></div>
<script type="text/javascript">
      var doBtn2 = document.getElementById('doBtn2')
          , status2 = document.getElementById('status2');

      function sleep2(ms) {
        var start = new Date();
        while (new Date() - start <= ms) {}
      }
      
      doBtn2.onclick = function(e) {
          status2.innerHTML = 'doing...please wait...'; 
          setTimeout(function() {
            sleep2(3000); 
            status2.innerHTML = 'done'; 
          }, 100); 
          return false;
      };
</script>
複製代碼

在“doing...please wait...”後面加了個setTimeout,延時執行,給了瀏覽器渲染的時間,這個時候會顯示出“doing...please wait...”的字樣,然後執行sleep函數,最後顯示“done”。

 

後面有網友發現在firefox中不起作用,的確有這個問題,後面我修改了一下代碼,將局部變量的聲明,onclick的綁定放到了window.onload事件中,等頁面結構加載完成後,我再做腳本操作。

複製代碼
<script type="text/javascript">
      function sleep(ms) {
        //...
      }
      window.onload = function() {
            var doBtn = document.getElementById('doBtn'),
            status = document.getElementById('status');
            
            var doBtn2 = document.getElementById('doBtn2')
              , status2 = document.getElementById('status2');
              
            doBtn.onclick = function(e) {
                //...
            };
            doBtn2.onclick = function(e) {
                //...
            };
      };
</script>
複製代碼

 

 

demo下載:

http://download.csdn.net/download/loneleaf1/7956191

 

http://ejohn.org/blog/how-javascript-timers-work/  How JavaScript Timers Work
http://heroicyang.com/2012/08/28/javascript-event-loop/ JavaScript Event Loop 淺析
http://www.2cto.com/kf/201204/129337.html JavaScript可否多線程?
http://www.nowamagic.net/librarys/veda/detail/787 優化js腳本設計,防止瀏覽器假死
http://www.ruanyifeng.com/blog/2013/10/event_loop.html 什麼是 Event Loop?

發佈了20 篇原創文章 · 獲贊 2 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章