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都是這樣)