js中setTimeout()時間參數設置為0的探討


起因源於幾道前端筆試題:

var fuc = [1,2,3];
for(var i in fuc){
setTimeout(function(){console.log(fuc[i])},0); console.log(fuc[i]); }

for(var i = 0; i < 3; i ++) {
(function (x) {
setTimeout(function () {
console.log(i);
});
console.log(i);
})(i);
}

 

問:控制台會如何打印?

chrome打印結果如下:

雖然setTimeout函數在每次循環的開始就調用了,但是卻被放到循環結束才執行,循環結束,i=3,接連打印了3次3。

這里涉及到javascript單線程執行的問題:javascript在瀏覽器中是單線程執行的,必須在完成當前任務后才執行隊列中的下一個任務。

另外,對於javascript還維護着一個setTimeout隊列,未執行的setTimeout任務就按出現的順序放到setTimeout隊列,等待普通的任務隊列中的任務執行完才開始按順序執行積累在setTimeout中的任務。

所以在這個問題里,會先打印1 2 3,而將setTimeout任務放到setTimeout任務隊列,等循環中的打印任務執行完了,才開始執行setTimeout隊列中的函數,所以在最后會接着打印3次3。

 

由此,可以知道雖然設置為0秒后執行任務,實際上是大於0秒才執行的。可是這有什么用呢?

用處就在於我們可以改變任務的執行順序!因為瀏覽器會在執行完當前任務隊列中的任務,再執行setTimeout隊列中積累的的任務。

通過設置任務在延遲到0s后執行,就能改變任務執行的先后順序,延遲該任務發生,使之異步執行。

 

網上有個比較有意思的案例:

復制代碼
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
  <head>
    <title>
      圖
    </title>
    <meta charset="utf-8">
  </head>
  <body>
  <p>
    <input type="text" id="input" value=""/>
    <span id="preview"></span>
  </p>
  </body>   
<script type="text/javascript">
(function(){
  function $(id){
    return document.getElementById(id);
  }
  $('input').onkeypress = function(){
    $('preview').innerHTML = this.value;
  }
})();
</script>
</html>
 
復制代碼

 

這個keypress函數原意是監聽到用戶輸入字符串就將其完整的顯示出來,但是奇怪的是最后一個字符串總是沒能顯示出來:

,

但是只要改下onkeypress函數就好:

  $('input').onkeypress = function(){
    setTimeout(function(){$('preview').innerHTML = $('input').value;},0);
  }

將onkeypress里面的事件延遲到瀏覽器更新相關DOM元素的狀態之后執行,這是就能顯示出所有字符串了,如下:

 

setTimeout()是用來改變任務執行順序的,難道就沒有其他代替的方法了嗎?答案是有的。

將監聽keypress事件改為監聽keyup事件,一樣能達到同樣的效果:

$('input').onkeyup = function(){
  $('preview').innerHTML = $('input').value;
}

 

這里跟key事件的執行順序有關:

 

復制代碼
(function(){
  function $(id){
    return document.getElementById(id);
  }
  function log(a){
    console.log(a);
  }
  $('input').onkeypress = function(){
    log("keypress: "+this.value);
  }
  $('input').onkeyup = function(){
    log("keyup: "+this.value);
  }
  $('input').onkeydown=function(){
    log("keydown: "+this.value);
  }
})();
復制代碼

 

當用鍵盤輸入1后,控制台打印結果如下:

也就是先執行keydown事件,再執行keypress事件,執行keypress事件之后改變dom元素的狀態(這里是input的value改變了),再執行keyup。

這也解釋了為什么通過監聽keyup事件就能正確且及時的打印出input的值。

而keypress事件發生時,dom元素的狀態還未改變,keypress事件之后dom元素的狀態才發生改變,通過setTimeout延遲執行就能達到期望的結果了。

測試過chrome,firefox,ie表現一致。

 

再來看看另一個例子,稍微有些改動,來自http://blog.csdn.net/lsk_jd/article/details/6080772

復制代碼
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
  <head>
    <meta name="generator" content="HTML Tidy for HTML5 (experimental) for Windows https://github.com/w3c/tidy-html5/tree/c63cc39">
    <title>
      圖
    </title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="utf-8">
    <meta name="Description" content="SUperman">
    <style type="text/css">
    </style>
  </head>
  <body>
    <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>
  </body>   
<script type="text/javascript">
(function(){

  function get(id){
    return document.getElementById(id);
  }
  function log(a){
    console.log(a)
  }
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(); } 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();},0); } } })(); </script> </html>
復制代碼

 

該例子中,未使用setTimeout生成的input沒有獲得focus,而使用了setTimeout的input可以獲得focus,這也是和setTimeout改變任務執行順序有關。

可是生成的input為什么不會focus呢?是這個focus沒執行嗎,通過給focus綁定一個事件就可以知道事實是怎樣:

復制代碼
get('makeinput').onmousedown=function(){
      var input = document.createElement('input');
      input.setAttribute('type', 'text');
      input.setAttribute('value', 'test1');
      get('inpwrapper').appendChild(input);
      input.onfocus=function(){    //給生成的input綁定focus事件
        log("focused");
      }
      input.focus();
    }
復制代碼

 

結果發現控制台有打印"focused"的,進一步猜測未使用setTimeout生成的input獲取了focus又失去focus,改下代碼看看mouse事件和focus事件的執行順序:

復制代碼
    get('makeinput').onmousedown=function(){
      var input = document.createElement('input');
      input.setAttribute('type', 'text');
      input.setAttribute('value', 'test1');
      get('inpwrapper').appendChild(input);
      input.onfocus=function(){
        log("focused");
      }
      input.focus();
      log("down");
    }
    get('makeinput').onfocus = function(){
      log("focus");
    }
    get('makeinput').onclick = function(){
     log("click");
    }
    get('makeinput').onmouseup=function(){
      log("up");
    }
復制代碼


結果如下:

 

可見先執行mousedown事件,然后生成的input獲得focus,接着按鈕獲得focus,接着執行mouseup事件,再執行click事件,和key的3個事件的執行順序有些不同。

看到這里恍然大悟,這個生成的input確實是獲得了focus,但是隨之失去focus,因為按鈕的mousedown事件緊跟着按鈕的focus事件,被按鈕奪取了focus。

將 input.focus(); 改為 setTimeout(function(){input.focus();},0);得到結果為:

 

也就是通過延遲執行input獲取focus事件,最終就是生成的input獲取了focus。

這樣的話,通過綁定mouseup,click事件同樣能使生成的input奪取按鈕的focus事件,事實證明確實如此,就不貼代碼上來了。

如果綁定focus事件會怎樣呢?這一次,chrome和ie站在了統一戰線,都是生成一個獲取了focus的input,但有趣的是firefox居然生成了2個。

不過ie反應也不算正常,綁定mousedown時不使用setTimeout的輸出是這樣的orz:

 

 

 

也就是focus事件好像不執行了,由於沒有執行按鈕的focus事件,生成的input是獲得focus的,也就是不需要延遲執行函數,生成的input也能獲取focus。

趕緊將input.focus()注釋掉,發現打印出"focused"的地方替換成了"focus",將生成input且獲取focus的代碼綁定到其他mouse事件和focus事件得到的結果比較復雜,就暫且不做深究,但是可以知道按鈕都能執行focus事件,除了綁定mousedown事件忽略了foucs事件。

原因不詳,應該是和各瀏覽器對focus的具體實現有差異吧。看到的朋友若知道什么原因,歡迎告知。

 

不過折騰了這么久,敢肯定setTimeout確實是實現異步執行的利器。這次對setTimeout的探討也沒有很深入,不過同時也弄清楚了key事件、mouse事件和focus事件的執行順序。

 本人正在學習和摸索中,如有錯誤,歡迎指正!

 

-------------------------------轉載注明出處^_^:  http://www.cnblogs.com/suspiderweb/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM