1. 程式人生 > >【js】關於 setTimeout(0)所引發的……

【js】關於 setTimeout(0)所引發的……

在看vue的時候,看到了vue關於非同步更新的原理,裡面提到了setTimeout(0)。於是我想起來之前面試的時候被問過這個問題,並且在很久之後我隨便查了查敷衍了事= =,到底是敷衍誰。於是昨天又去google了一下這個問題……果然發現了很多不得了的事(笑哭)。

之前知道,js是單執行緒的,所以在一段時間內只能執行一個任務。然後我們又知道settimeout和setinterver都是存在時延的,因為可能在計時器到達時間後,執行緒正在做其他的任務。所以要等到這個任務結束了才開始執行回撥函式。我以為是因為這樣,所以settimeout(0)由於時延,導致了在下一條語句之後才能執行。再見。回想了下,那時候用的是百度,以後一定要告別度娘……

首先要說的一個是,現代瀏覽器一般要求settimeout這樣的定時器,時延時間在4毫秒以上,如果小於4毫秒,就要往上加。(另外對於setInterval,H5規定最少時間為10毫秒)所以之前的理解從一定程度上……沒什麼問題TAT。

但是如果從一個稍微複雜的情況下去分析,那就可以發現……這個問題下的js機制相關的問題了。

以下是例子:

例子

這是比較多引用的一個例子,原始碼在這兒:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">


 <html>


 <head>


 <title>setTimeout</title>


 <script type="text/javascript" >
(function(){
 
 function get(id){
  return document.getElementById(id);
 }
 
 window.onload = function(){
  get('makeinput').onmousedown = function(){
   var input = document.createElement('input');
   input.setAttribute('type', 'text');
   input.setAttribute('value', 'test1');
   get('inpwrapper').appendChild(input);
   input.focus();
   input.select();
  }
  get('makeinput2').onmousedown = function(){
   var input = document.createElement('input');
   input.setAttribute('type', 'text');
   input.setAttribute('value', 'test1');
   get('inpwrapper2').appendChild(input);
   setTimeout(function(){
    input.focus();
    input.select();
   }, 0);
  }
  get('input1').onkeypress = function(){
   get('preview1').innerHTML = this.value;
  }
  get('input2').onkeypress = function(){
                               setTimeout(function(){
   get('preview2').innerHTML = get('input2').value;
                              },0 );
  }
 }
})();


</script>


</head>


<body>
<h1><code>DEMO1</code></h1>
<h2>1、未使用 <code>setTimeout</code>(未選中文字框內容)</h2>
<button id="makeinput">生成 input</button>
<p id="inpwrapper"></p>
<h2>2、使用 <code>setTimeout</code>(立即選中文字框內容)</h2>
<button id="makeinput2">生成 input</button></h2>
<p id="inpwrapper2"></p>


--------------------------------------------------------------------------
<h1><code>DEMO2</code></h1>
<h2>1、未使用 <code>setTimeout</code>(只有輸入第二個字元時,前一個字元才顯示出來)</h2>
<input type="text" id="input1" value=""/><div id="preview1"></div>
<h2>2、使用 <code>setTimeout</code>(輸入時,字元同時顯示出來)</h2>


<input type="text" id="input2" value=""/><div id="preview2"></div>


</body>


</html>
看了這個例子覺得很神奇,但是看到原文得出的結論覺得匪夷所思。結論認為,在第一個例子,onmousedown的時候,由於單執行緒,在執行onmousedown,所以就拋棄了focus和select。這個說法顯然是非常不正確的。

這時候提到js的執行機制:event loop。

瀏覽器最起碼有三個執行緒,js執行緒,GUI執行緒,瀏覽器事件觸發執行緒。js執行緒和GUI執行緒是互斥的,當頁面需要渲染(迴流,重繪等)的時候,js執行緒就掛起。js執行的時候GUI執行緒就先快取。瀏覽器事件觸發捕獲相關事件,然後把事件加入到js事件佇列尾部。

把js執行緒看做一個佇列,裡面執行的都是同步的任務。對於非同步的任務,當非同步的任務到了可以執行的時候(比如事件觸發了,計時器的時間到了),就把這個任務取出來加到一個佇列裡面。當主執行緒中的任務執行完了,就到這個非同步佇列裡面取任務執行。

如上所述的過程就是一個事件迴圈的過程,一系列event事件做完了,再取非同步的佇列做。做完了再來一輪。(我覺得是按時間順序,然後先處理同步事件,再處理非同步事件這樣)

所以上面那個例子,沒有執行select()和focus()只是因為執行之後,btn.focus又重新佔據了焦點。

關於settimeout(0),我之前一直以為是像++x++這樣有點無趣,有點變態地考察知識點的方式。然而其實人家還是有正經用途的。由於事件迴圈機制,可以利用settimeout(0)實現非同步,並且可以改變一些事件實現的順序。比如在用promise的時候,通過設定settimeout(0)把同步的呼叫變成非同步的。詳情可以參見promise指南中的例子

參考資料: