你真的懂JavaScript計時器嗎?

一坨翔發表於2018-08-06

在今天之前我一直以為setTimeout這個函式是非同步的,無意中看到了一篇關於setTimeout的文章,發現自己以前的認識全是錯誤的,趕緊總結下。


先看一段程式碼:







var start = new Date();







setTimeout(function(){







var end = new Date();







console.log("Time elapsed: ", end - start, "ms");







}, 500);













while (new Date - start <= 1000)







{













}



執行這段指令碼可以看到:Time elapsed的值大概在1001ms左右,肯定會超過1000ms。也就是說:setTimeout失效了,指定的函式並沒有在500ms後執行,而是延遲到1000ms後才執行。


再看一段程式碼:







function a()







{







setTimeout(function(){console.log(1);},0);







console.log(2);







}







a();



執行這段指令碼可以看到:先列印2後列印1,我們在setTimeout裡面指定了0ms,希望能立即執行,但是實際上沒有效果。


想要理解上面的2段程式碼,我們得了解一下javascript中setTimeout的實現原理。首先牢記一點:JavaScript 是單執行緒執行的,也就是無法同時執行多段程式碼。下面這段解釋來自這篇部落格

        JavaScript是單執行緒執行的,無法同時執行多段程式碼。當某一段程式碼正在執行的時候,所有後續的任務都必須等待,形成一個佇列。一旦當前任務執行完畢,再從佇列中取出下一個任務,這也常被稱為 “阻塞式執行”。所以一次滑鼠點選,或是計時器到達時間點,或是Ajax請求完成觸發了回撥函式,這些事件處理程式或回撥函式都不會立即執行,而是立即排隊,一旦執行緒有空閒就執行。假如當前 JavaScript執行緒正在執行一段很耗時的程式碼,此時發生了一次滑鼠點選,那麼事件處理程式就被阻塞,使用者也無法立即看到反饋,事件處理程式會被放入任務佇列,直到前面的程式碼結束以後才會開始執行。如果程式碼中設定了一個 setTimeout,那麼瀏覽器便會在合適的時間,將程式碼插入任務佇列,如果這個時間設為 0,就代表立即插入佇列,但不是立即執行,仍然要等待前面程式碼執行完畢。所以 setTimeout 並不能保證執行的時間,是否及時執行取決於 JavaScript 執行緒是擁擠還是空閒。


也就是說setTimeout只能保證在指定的時間過後將任務(需要執行的函式)插入佇列等候,並不保證這個任務在什麼時候執行。執行javascript的執行緒會在空閒的時候,自行從佇列中取出任務然後執行它。javascript通過這種佇列機制,給我們製造一個非同步執行的假象。







var start = new Date();







setTimeout(function(){







var end = new Date();







console.log("Time elapsed: ", end - start, "ms");







}, 500);













console.log("task finished.");



我們之所以會感覺到這段程式碼是在非同步執行,這是因為javascript執行緒並沒有因為什麼耗時操作而阻塞,所以可以很快地取出排隊佇列中的任務然後執行它。

現在我們知道了setTimeout的原理了,現在看下setTimeout(0)的使用場景。下面這個例子來自這篇文章







<input type="text" onkeydown="show(this.value)">







<div></div>







<script type="text/javascript">







function show(val) {







document.getElementsByTagName(`div`)[0].innerHTML = val;







}







</script>



這裡繫結了 keydown 事件,意圖是當使用者在文字框裡輸入字元時,將輸入的內容實時地在 <div> 中顯示出來。但是實際效果並非如此,可以發現,每按下一個字元時,<div> 中只能顯示出之前的內容,無法得到當前的字元。







<input type="text" onkeydown="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>



這段程式碼使用了setTimeout(0)就可以實現需要的效果了。這裡其實涉及2個任務,1個是將鍵盤輸入的字元回寫到輸入框中,一個是獲取文字框的值將其寫入div中。第一個是瀏覽器自身的預設行為,一個是我們自己編寫的程式碼。很顯然,必須要先讓瀏覽器將字元回寫到文字框,然後我們才能獲取其內容寫到div中。改變順序,這這正是setTimeout(0)的作用。



相關文章