高級定時器
關(guān)于定時器要記住的最重要的事情是:指定的時間間隔表示何時將定時器的代碼添加到隊列,而不是何時實際執(zhí)行代碼车摄。
定時器對隊列的工作方式是,當(dāng)特定時間過去后將代碼插入。注意没宾,給隊列添加代碼并不意味著對它立刻執(zhí)行忍法,而只能表示它會盡快執(zhí)行。設(shè)定一個 150ms 后執(zhí)行的定時器不代表到了 150ms代碼就立刻執(zhí)行榕吼,它表示代碼會在 150ms 后被加入到隊列中饿序。如果在這個時間點(diǎn)上,隊列中沒有其他東西羹蚣,那么這段代碼就會被執(zhí)行原探,表面上看上去好像代碼就在精確指定的時間點(diǎn)上執(zhí)行了。其他情況下顽素,代碼可能明顯地等待更長時間才執(zhí)行咽弦。
定時器僅僅只是計劃代碼在未來的某個時間執(zhí)行。執(zhí)行的時間是不能保證的胁出,因為在頁面的生命周期中型型,不同時間可能有其他代碼在控制 JavaScript 進(jìn)程。在頁面下載完后的代碼運(yùn)行全蝶、事件處理程序闹蒜、Ajax 回調(diào)函數(shù)都必須使用同樣的線程來執(zhí)行。實際上抑淫,瀏覽器負(fù)責(zé)進(jìn)行排序绷落,指派某段代碼在某個時間點(diǎn)運(yùn)行的優(yōu)先級。
可以把 JavaScript 想象成在時間線上運(yùn)行的始苇。當(dāng)頁面載入時砌烁,首先執(zhí)行是任何包含在 < script> 元素中的代碼,通常是頁面生命周期后面要用到的一些簡單的函數(shù)和變量的聲明催式,不過有時候也包含一些初始數(shù)據(jù)的處理函喉。在這之后,JavaScript 進(jìn)程將等待更多代碼執(zhí)行荣月。當(dāng)進(jìn)程空閑的時候管呵,下一個代碼會被觸發(fā)并立刻執(zhí)行。例如喉童,當(dāng)點(diǎn)擊某個按鈕時撇寞, onclick 事件處理程序會立刻執(zhí)行,只要 JavaScript 進(jìn)程處于空閑狀態(tài)堂氯。這樣一個頁面的時間線類似于下圖(引自高程三)
下面是有循環(huán)的代碼
<div id="test">2222222222</div>
<script>
var oTest=document.getElementById("test");
oTest.onclick=function(){
var nowTime=new Date();
setTimeout(function(){
console.log("定時器的代碼");
console.log(new Date()-nowTime);//534
},100);
for(var i=0;i<1000000000;i++){}
var clickTime=new Date();
console.log(clickTime-nowTime);//532
}
</script>
點(diǎn)擊后蔑担,會發(fā)現(xiàn),并不是過100毫秒執(zhí)行的咽白;如果去掉for循環(huán)啤握,那是差不多的;
重復(fù)定時器 setInterval
使用 setInterval() 創(chuàng)建的定時器確保了定時器代碼規(guī)則地插入隊列中晶框。這個方式的問題在于排抬,定時器代碼可能在代碼再次被添加到隊列之前還沒有完成執(zhí)行懂从,結(jié)果導(dǎo)致定時器代碼連續(xù)運(yùn)行好幾次,而之間沒有任何停頓蹲蒲。
幸好番甩,JavaScript 引擎夠聰明,能避免這個問題届搁。
當(dāng)使用setInterval() 時缘薛,
- 1、僅當(dāng)沒有該定時器的任何其他代碼實例時卡睦,才將定時器代碼添加到隊列中宴胧。
這確保了定時器代碼加入到隊列中的最小時間間隔為指定間隔。
但是由此引發(fā)了setInterval 定時器規(guī)則有兩個問題:
- 1表锻、某些間隔會被跳過恕齐;
- 2、多個定時器的代碼執(zhí)行之間的間隔可能會比預(yù)期的兴惭贰显歧;
因為規(guī)則是:僅當(dāng)沒有該定時器的任何其他代碼實例時,才將定時器代碼添加到隊列中码耐。
如果設(shè)置一個重復(fù)定時器 (setInterval)追迟; 處理的時間為300毫秒;但是間隔時間是200毫秒骚腥;間隔時間小于單次定時器內(nèi)代碼的處理時間;就會同時出現(xiàn)跳過間隔且連續(xù)運(yùn)行定時器代碼的情況瓶逃。(隊列中已經(jīng)有一次相同的定時器時束铭,第二次添加會被忽略)
高程三的例子:
假設(shè),某個 onclick 事件處理程序使用 setInterval() 設(shè)置了一個 200ms 間隔的重復(fù)定時器厢绝。如果事件處理程序花了 300ms多一點(diǎn)的時間完成契沫,同時定時器代碼也花了差不多的時間,就會同時出現(xiàn)跳過間隔且連續(xù)運(yùn)行定時器代碼的情況咸作。
這個例子中的第 1 個定時器是在 205ms 處添加到隊列中的陨晶,但是直到過了 300ms 處才能夠執(zhí)行辞友。當(dāng)執(zhí)行這個定時器代碼時,在 405ms處又給隊列添加了另外一個副本会通。在下一個間隔,即 605ms 處娄周,第一個定時器代碼仍在運(yùn)行涕侈,同時在隊列中已經(jīng)有了一個定時器代碼的實例。所以在這個時間點(diǎn)上的定時器代碼不會被添加到隊列中煤辨。
結(jié)果在 5ms處添加的定時器代碼結(jié)束之后裳涛,405ms 處添加的定時器代碼就立刻執(zhí)行木张。
解決的辦法
使用鏈?zhǔn)?setTimeout() 調(diào)用
setTimeout(function(){
//處理中
setTimeout(arguments.callee, 400);
}, 500);
在前一個定時器代碼執(zhí)行完之前,不會向隊列插入新的定時器代碼端三,確保不會有任何缺失的間隔舷礼。
-
而且,它可以保證在下一次定時器代碼執(zhí)行之前郊闯,至少要等待指定的間隔妻献,避免了連續(xù)的運(yùn)行。這個模式主要用于重復(fù)定時器虚婿,
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="test.css"/>
<style>
#test{
width: 200px;height: 200px;background-color: #002d32;
position: absolute;
top: 300px;
left: 20px;
}
</style>
</head>
<body>
<div id="test">2222222222</div>
<script>
var div = document.getElementById("test");
var left;
var timer=setTimeout(function(){
//處理中
left = parseFloat(window.getComputedStyle(div,null).left)+5;
console.log(left);
div.style.left = left + "px";
if (left < 200) {
setTimeout(arguments.callee, 500);
console.log("star agin ");
}else{
console.log("clear Timeout");
clearTimeout(timer);
}
}, 10);
</script>
</body>
</html>
JavaScript 動畫中使用這個模式很常見旋奢。