setTimeout()
可用來延遲執行一段代碼,如:
setTimeout( function() { alert("Hello World"); }, 1000) //延時1秒
今天在網上看到了 setTimeout(fn, 0)
的用法,感到有些疑惑,不明白它和直接執行 fn()
有什麼區別,遂去搜集了一下相關的資料,順便分享分享。
先看一段代碼:
function a() { setTimeout( function(){ alert(1) }, 0); alert(2); } a();
代碼中的 setTimeout
設爲 0,也就是延遲 0ms,看上去是不做任何延遲立刻執行,即依次彈出 “1”、“2”。但實際的執行結果確是 “2”、“1”。其中的原因得從 setTimeout
的原理說起:
JavaScript 是單線程執行的,也就是無法同時執行多段代碼,當某一段代碼正在執行的時候,所有後續的任務都必須等待,形成一個隊列,一旦當前任務執行完畢,再從隊列中取出下一個任務。這也常被稱爲 “阻塞式執行”。所以一次鼠標點擊,或是計時器到達時間點,或是 Ajax 請求完成觸發了回調函數,這些事件處理程序或回調函數都不會立即運行,而是立即排隊,一旦線程有空閒就執行。假如當前 JavaScript 進程正在執行一段很耗時的代碼,此時發生了一次鼠標點擊,那麼事件處理程序就被阻塞,用戶也無法立即看到反饋,事件處理程序會被放入任務隊列,直到前面的代碼結束以後纔會開始執行。如果代碼中設定了一個
setTimeout
,那麼瀏覽器便會在合適的時間,將代碼插入任務隊列,如果這個時間設爲 0,就代表立即插入隊列,但不是立即執行,仍然要等待前面代碼執行完畢。所以
setTimeout
並不能保證執行的時間,是否及時執行取決於 JavaScript 線程是擁擠還是空閒。
至於這樣的寫法有什麼作用,看下面的例子。
<input type="text" οnkeydοwn="show(this.value)"> <div></div> <script type="text/javascript"> function show(val) { document.getElementsByTagName('div')[0].innerHTML = val; } </script>
這裏綁定了 keydown
事件,意圖是當用戶在文本框裏輸入字符時,將輸入的內容實時地在 <div> 中顯示出來。但是實際效果並非如此,你可以在下面的示例中測試:
可以發現,每按下一個字符時,<div> 中只能顯示出之前的內容,無法得到當前的字符。這時就可以利用 setTimeout(0)
:
<input type="text" οnkeydοwn="var self=this; setTimeout(function() {show(self.value)}, 0)"> <div></div> <script type="text/javascript"> function show(val) { document.getElementsByTagName('div')[0].innerHTML = val; } </script>
原因是,當用戶按下按鍵的時候,JavaScript 引擎需要執行 keydown
的事件處理程序,然後更新文本框的 value
值,這兩件事也需要按順序來,事件處理程序執行時,更新
value
值的任務則進入隊列等待。所以我們在 keydown
的事件處理程序裏是無法得到更新後的
value
的,利用 setTimeout
,我們把取 value
的操作放入隊列,放在更新
value
值以後,這樣便達到了目的。示例如下,可以發現已經能夠實時顯示輸入的內容。
有人可能會想到利用綁定 keyup
事件來解決,但是 onkeyup
有一個問題,就是當一直按着某個鍵不放時,也會無法得到輸入內容,因爲此時不斷地觸發
keydown
和 keypress
,直到用戶鬆起時纔會觸發 keyup
。當然,示例所用的
keydown
也存在一些小缺點,比如用戶如果使用右鍵粘貼,則無法得到粘貼的內容。
input
事件,當文本框或 textarea
的 value
發生變化時,就會觸發此事件,對粘貼也可以很好地兼容。至於 IE9 之前的瀏覽器,需要使用專有的
onpropertychange
事件