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

原由源於一道前端筆試題:javascript

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

 

問:控制檯會如何打印?css

chrome打印結果以下:html

 

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

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

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

因此在這個問題裏,會先打印1 2 3,而將setTimeout任務放到setTimeout任務隊列,等循環中的打印任務執行完了,纔開始執行setTimeout隊列中的函數,因此在最後會接着打印3次3。git

 

由此,能夠知道雖然設置爲0秒後執行任務,其實是大於0秒才執行的。但是這有什麼用呢?github

用處就在於咱們能夠改變任務的執行順序!由於瀏覽器會在執行完當前任務隊列中的任務,再執行setTimeout隊列中積累的的任務。web

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

 

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

<!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>一、未使用 <code>setTimeout</code></h2>
    <button id="makeinput">生成 input</button>
    <p id="inpwrapper"></p>
    <h2>二、使用 <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/

相關文章
相關標籤/搜索