JavaScript Timer?
Mon, 19 Jan 2009 13:39:04 +0800幾天前在YUI Blog上看到有Zakas的新書免費試閱,就下載來看了一下。他最後提到了一些必須要了解的timer概念,對於使用setTimeout或是setInterval函數很有幫助。
主要的問題在於,JavaScript的實作主要都是基於single thread模型,而用一個queue的結構來處理非同步的執行(包含DOM上面發生的事件、AJAX的事件、setTimeout的事件、setInterval的事件觸發等等),所以他只保證盡量在指定的時候執行,但不保證時間的精確。jQuery的作者John Resig在他的blog中有一篇文章做了不錯的說明:How JavaScript Timers Work。事實上,所有的程式都是放在一個queue裡面依序執行的,所以只要用一個alert()來暫停執行,其他所有應該要執行的程式也都受影響而停下來。
以下用之前做的在指定時間執行動作的程式來做測試,程式碼如下:
<html> <body> <div id="panel"><img src="bg/char1.gif"></div> <input type="button" value="delay"> <script> (function(){ var component = { target: null, init: function(x) {this.target = x; return this;}, click: function(f) {this.target.onclick = f; return this;}, html: function(t) {this.target.innerHTML = t; return this;}, actionSequencer: function(t) { var target = this.target; var duration = t.duration|0; var action = t.action|[]; action = t.action.sort(function(a,b){return a[0]-b[0];}); var drop = t.drop|false; var start; var curr = start = new Date().getTime(); var timer = true; var currTimeUpdater = window.setInterval(function(){ if(timer) { curr = new Date().getTime(); } else { window.clearInterval(currTimeUpdater); } },1); var timerStop = window.setInterval(function(){ if ((curr - start) > duration) { timer = false; window.clearInterval(timerStop); } },1); var actionHandler = window.setInterval(function(){ if(timer) { if(action.length>0&&(curr-start) > action[0][0]) { if(!drop) { var a = window.setTimeout(function(){ window.clearTimeout(a); action[0][1](target); action.shift(); }, 1); } else { for(var i=0; i<action.length; i++) { if(action[i][0] > curr-start) { var b = window.setTimeout((function(j){ window.clearTimeout(b); return function(){ action[j][1](target); };})(i),1); return; } } } } } else { window.clearInterval(actionHandler); } },1); return this; } }; $ = function(x) { return component.init(x); }; })(); $(document.getElementById("panel")).click(function(){ $(this).actionSequencer( { duration: 1100, action: [ [20, function(target){$(target).html('<img src="bg/char1.gif">');}], [40, function(target){$(target).html('<img src="bg/char2.gif">');}], [60, function(target){$(target).html('<img src="bg/char3.gif">');}], [80, function(target){$(target).html('<img src="bg/char5.gif">');}], [100, function(target){$(target).html('<img src="bg/char4.gif">');}], [120, function(target){$(target).html('<img src="bg/char6.gif">');}], [140, function(target){$(target).html('<img src="bg/char7.gif">');}], [160, function(target){$(target).html('<img src="bg/char8.gif">');}], [180, function(target){$(target).html('<img src="bg/char1.gif">');}] [200, function(target){$(target).html('<img src="bg/char1.gif">');}], [220, function(target){$(target).html('<img src="bg/char2.gif">');}], [240, function(target){$(target).html('<img src="bg/char3.gif">');}], [260, function(target){$(target).html('<img src="bg/char5.gif">');}], [280, function(target){$(target).html('<img src="bg/char4.gif">');}], [300, function(target){$(target).html('<img src="bg/char6.gif">');}], [320, function(target){$(target).html('<img src="bg/char7.gif">');}], [340, function(target){$(target).html('<img src="bg/char8.gif">');}], [360, function(target){$(target).html('<img src="bg/char1.gif">');}] [380, function(target){$(target).html('<img src="bg/char1.gif">');}], [400, function(target){$(target).html('<img src="bg/char2.gif">');}], [420, function(target){$(target).html('<img src="bg/char3.gif">');}], [440, function(target){$(target).html('<img src="bg/char5.gif">');}], [460, function(target){$(target).html('<img src="bg/char4.gif">');}], [480, function(target){$(target).html('<img src="bg/char6.gif">');}], [500, function(target){$(target).html('<img src="bg/char7.gif">');}], [520, function(target){$(target).html('<img src="bg/char8.gif">');}], [540, function(target){$(target).html('<img src="bg/char1.gif">');}] [560, function(target){$(target).html('<img src="bg/char1.gif">');}], [580, function(target){$(target).html('<img src="bg/char2.gif">');}], [600, function(target){$(target).html('<img src="bg/char3.gif">');}], [620, function(target){$(target).html('<img src="bg/char5.gif">');}], [640, function(target){$(target).html('<img src="bg/char4.gif">');}], [660, function(target){$(target).html('<img src="bg/char6.gif">');}], [680, function(target){$(target).html('<img src="bg/char7.gif">');}], [700, function(target){$(target).html('<img src="bg/char8.gif">');}], [720, function(target){$(target).html('<img src="bg/char1.gif">');}] [740, function(target){$(target).html('<img src="bg/char1.gif">');}], [760, function(target){$(target).html('<img src="bg/char2.gif">');}], [780, function(target){$(target).html('<img src="bg/char3.gif">');}], [800, function(target){$(target).html('<img src="bg/char5.gif">');}], [820, function(target){$(target).html('<img src="bg/char4.gif">');}], [840, function(target){$(target).html('<img src="bg/char6.gif">');}], [860, function(target){$(target).html('<img src="bg/char7.gif">');}], [880, function(target){$(target).html('<img src="bg/char8.gif">');}], [900, function(target){$(target).html('<img src="bg/char1.gif">');}] [920, function(target){$(target).html('<img src="bg/char1.gif">');}], [940, function(target){$(target).html('<img src="bg/char2.gif">');}], [960, function(target){$(target).html('<img src="bg/char3.gif">');}], [980, function(target){$(target).html('<img src="bg/char5.gif">');}], [1000, function(target){$(target).html('<img src="bg/char4.gif">');}], [1020, function(target){$(target).html('<img src="bg/char6.gif">');}], [1040, function(target){$(target).html('<img src="bg/char7.gif">');}], [1060, function(target){$(target).html('<img src="bg/char8.gif">');}], [1080, function(target){$(target).html('<img src="bg/char1.gif">');}] ], drop: false }); }); $(document.getElementsByTagName('input')[0]).click(function(){alert('test');}); </script> </body> </html>
測試連結:http://www.fillano.idv.tw/test406.html。跟之前文章中的例子差別主要是在加長執行時間讓小人多轉幾次,並且調整圖片顯示的framerate。在小人轉的時候按下delay按鈕,alert出現的時候,小人也不轉了......不轉的原因就是因為在alert的時候程式暫停執行,當按下確定按鈕時,duration如果已經超過了設定的時間,也就不繼續轉下去。(在Firefox3.0.5、Google Chrome 1.0、Safari 3.2.1、Webkit Nightly Build r39553底下執行結果都一樣。另外如果反應夠快,在alert跳出來時來得及按下確定,那還是會接著指定的時間及動作轉下去,但是就不是連續的了。)
測試結果很明顯,JavaScript是Single Thread的,不論是ajax、各種dom事件、setTimeout、setInterval都不會new一個Thread的。(至少對於我測試過的幾個implementation都是這樣)