Javascript 中會(huì)經(jīng)常用到 setTimeout 來(lái)推遲一個(gè)函數(shù)的執(zhí)行,如:
setTimeout(function(){
alert("Hello World");
},1000);
會(huì)在執(zhí)行到這句話后延遲 1 秒鐘來(lái)彈出 alert 窗口。
那么再看這一段:
function test(){
setTimeout(function() {alert(1)}, 0);
alert(2);
}
test();
注意這段代碼中的 setTimeout 延遲設(shè)為了 0,就是延遲 0 毫秒,貌似是不做任何延遲立刻執(zhí)行,即 1,2。但實(shí)際的執(zhí)行結(jié)果確是 2,1。為什么?這得從 Javascript 調(diào)用堆棧(call stack)和 setTimeout 的功能說(shuō)起。
首先,JavaScript 是單線程的,即同一時(shí)間只執(zhí)行一條代碼,所以每一個(gè) JavaScript 代碼執(zhí)行塊會(huì) “阻塞” 其它異步事件的執(zhí)行。其次,和其他的編程語(yǔ)言一樣,Javascript 中的函數(shù)調(diào)用也是通過(guò)堆棧實(shí)現(xiàn)的。在執(zhí)行函數(shù) test 的時(shí)候,test 先入棧,如果不給 alert(1)加 setTimeout,那么 alert(1)第 2 個(gè)入棧,最后是 alert(2)。但現(xiàn)在給 alert(1)加上 setTimeout 后,alert(1)就被加入到了一個(gè)新的堆棧中等待,并 “盡可能快” 的執(zhí)行。這個(gè)盡可能快就是指在 a 的堆棧完成后就立刻執(zhí)行,因此實(shí)際的執(zhí)行結(jié)果就是先 alert(2),再 alert(1)。在這里 setTimeout 實(shí)際上是讓 alert(1)脫離了當(dāng)前函數(shù)調(diào)用堆棧。
看下面一個(gè)例子:
<input name="input" onkeydown="alert(this.value)" type="text" value="a" />
這樣一段函數(shù)意圖是每輸入一個(gè)字符就把當(dāng)前 input 里的所有字符都 alert 出來(lái),但實(shí)際效果確是 alert 出按鍵之前的內(nèi)容。這里,我們就可以利用 setTimeout(0) 來(lái)實(shí)現(xiàn)。
<input onkeydown="var me=this; setTimeout(function(){alert(me.value)}, 0)" name="input" type="text" value="a" />
這樣當(dāng) onkeydown 事件觸發(fā)的時(shí)候,alert 就被放入了下一個(gè)調(diào)用堆棧,一旦 onkeydown 事件觸發(fā)的堆棧關(guān)閉后就開(kāi)始執(zhí)行。當(dāng)然瀏覽器還有個(gè) onkeyup 事件也可以實(shí)現(xiàn)我們的需求。
這樣的 setTimeout 用法在實(shí)際項(xiàng)目中還是會(huì)時(shí)常遇到。比如瀏覽器會(huì)聰明的等到一個(gè)函數(shù)堆棧結(jié)束后才改變 DOM,如果再這個(gè)函數(shù)堆棧中把頁(yè)面背景先從白色設(shè)為紅色,再設(shè)回白色,那么瀏覽器會(huì)認(rèn)為 DOM 沒(méi)有發(fā)生任何改變而忽略這兩句話,因此我們可以通過(guò) setTimeout 把 “設(shè)回白色” 函數(shù)加入下一個(gè)堆棧,那么就可以確保背景顏色發(fā)生過(guò)改變了(雖然速度很快可能無(wú)法被察覺(jué))。
總之,setTimeout 增加了 Javascript 函數(shù)調(diào)用的靈活性,為函數(shù)執(zhí)行順序的調(diào)度提供極大便利。