1. 程式人生 > >通過setTimeout理解js非同步原理

通過setTimeout理解js非同步原理

在上一篇的文章中介紹了js非同步原理,有的小夥伴可能還有些雲裡霧裡,在本篇文章中將以新手很容易用錯的setTimeout為例來解釋js的非同步原理。

首先我們看一個例子

<script>
  console.log(1);
  setTimeout(function(){
    console.log(2);
  },500);
  console.log(3);
  // 執行結果
  // 1
  // 3
  // 2
</script>

這裡通過setTimeout延遲500毫秒執行,所以結果是1,3,2。這看起來明明就是非同步操作啊,為什麼說是同步呢?我們接著看下一個例子。
<script>
  var date = new Date();
  console.log('first time: ' + date.getTime());
  setTimeout(function(){
      var date1 = new Date();
      console.log('second time: ' + date1.getTime() );
      console.log( date1.getTime() - date.getTime() );
  },1000);
  for(var i=0; i < 10000 ; i++){
      console.log(1);
  }
  // 執行結果
  // first time: 1524540272462
  // (10000)1
  // second time: 1524540274346
  // 1884
</script>

神奇的事情出現了,假設js是非同步,那麼結果應該是這樣的:

// first time: 1524540272462

//(x)1

// second time: 1524540273462

// 1000

//(10000-x)1

x為一秒內列印的1的數量。

但是實際結果settimeout並非是1000毫秒後執行的,而是1884毫秒。其原因是js是單執行緒,在列印完first time: 1524540272462後settimeout內的操作進入了“任務佇列”,而1000毫秒到了以後,因為當前js正在執行列印1的操作,故js會在1列印完之後,才將settimeout內操作壓進執行棧裡。所以實際上我們看到的結果1884毫秒的原因就在於次。

這個例子說明js仍舊是單執行緒而非多執行緒,在同一時間內js只能做一件事情。

下面我們通過下面的簡單例子也能夠看出js是單執行緒的。

<script>
  console.log('1');
  setTimeout(function(){
      console.log('2');
  },10);
  while(true){};
  // 執行結果
  // 1
</script>

瀏覽器打印出1後就卡死了,並沒有打印出2來。因為執行棧必須把當前任務完成,任務佇列裡的操作才能進入執行棧。而執行棧內的操作是死迴圈,所以瀏覽器卡死,2也不會被打印出來。

下面來看一種特殊情況,當setTimeout的時間為0的時候是不是立即執行的。

  console.log(1);
  setTimeout(function(){
      console.log(2);
  },0);
  console.log(3);
  // 執行結果
  // 1
  // 3
  // 2

結果證明即便延遲設為0,也不是立即執行的。因為setTimeout有一個最小執行時間,當指定的時間小於該時間時,瀏覽器會用最小允許的時間作為setTimeout的時間間隔,也就是說即使我們把setTimeout的毫秒數設定為0,被呼叫的程式也沒有馬上啟動。

setTimeout的最小時間間隔和瀏覽器及作業系統有關。在John Resig的《Javascript忍者的祕密》一書中提到--Browsers all have a 10ms minimum delay on OSX and a(approximately) 15ms delay on Windows.(在蘋果機上的最小時間間隔是10毫秒,在Windows系統上的最小時間間隔大約是15毫秒),另外,MDC中關於setTimeout的介紹中也提到,Firefox中定義的最小時間間隔(DOM_MIN_TIMEOUT_VALUE)是10毫秒,HTML5定義的最小時間間隔是4毫秒。