定時(shí)器和線程是如何工作的
JavaScript提供了兩種方式,用于創(chuàng)建定時(shí)器以及兩個(gè)相應(yīng)的清除方法熟嫩。這些方法都是window對象上的秦踪。
// 在一段時(shí)間(delay)之后執(zhí)行傳入的fn方法,并返回該定時(shí)器的唯一標(biāo)識
id=setTimeout(fn,delay);
// 在定時(shí)器還未觸發(fā)時(shí)掸茅,取消定時(shí)器
clearTimeout(id)
// 在每間隔一段時(shí)間(delay)之后都執(zhí)行傳入的fn方法椅邓,并返回該定時(shí)器的唯一標(biāo)識
id=setInterval(fn,delay);
// 取消間隔定時(shí)器
id=clearInterval(id)
由于JavaScript是單線程的,在特定的時(shí)間點(diǎn)只能運(yùn)行一個(gè)執(zhí)行代碼昧狮,而且也無法確定定時(shí)器處理程序到底是在什么時(shí)候執(zhí)行景馁,當(dāng)一個(gè)異步事件發(fā)生時(shí)(鼠標(biāo)單擊,定時(shí)器觸發(fā)逗鸣,或者是ajax返回函數(shù))合住,它會進(jìn)行排隊(duì),在線程空閑時(shí)才進(jìn)行執(zhí)行撒璧,并且每個(gè)瀏覽器的排隊(duì)機(jī)制是不同的透葛,瀏覽器不會對來自同一個(gè)setInterval()的多個(gè)回調(diào)進(jìn)行排隊(duì),同一時(shí)刻沪悲,將只會有一個(gè)來自同一setInterval()的回調(diào)在隊(duì)列中获洲。
interval間隔定時(shí)器并不是周期執(zhí)行的timeout定時(shí)器阱表,通過下面代碼可以看出差異殿如。
setTimeout(function repeat() {
/*主內(nèi)容代碼*/
setTimeout(repeat,10);
},10);
setInterval(function () {
/*主內(nèi)容代碼*/
},10);
在setTimeout()代碼中,要在前一個(gè)主題內(nèi)容代碼執(zhí)行結(jié)束并延遲10ms的時(shí)間后最爬,才能再次執(zhí)行setTimeout()涉馁。
而setInterval()代碼中,每間隔10ms就嘗試執(zhí)行主內(nèi)容代碼爱致,并不會關(guān)注上一次setInterval()中的主內(nèi)容代碼是如何執(zhí)行的烤送。
瀏覽器無法保證我們制定的延遲間隔,尤其是在間隔時(shí)間非常小的情況下糠悯,因?yàn)閳?zhí)行回調(diào)函數(shù)和代碼本身也要花費(fèi)時(shí)間帮坚。
處理昂貴的計(jì)算過程
下面代碼會創(chuàng)建240000個(gè)DOM節(jié)點(diǎn),并使用大量單元格來填充一個(gè)表格互艾。
var tbody = document.getElementsByTagName("tbody")[0];
for(var i=0;i<20000;i++){
var tr = document.createElement("tr");
for(var j=0;j<6;j++){
var td = document.createElement("td");
td.appendChild(document.createTextNode(i+","+j));
tr.appendChild(td);
}
tbody.appendChild(tr);
}
執(zhí)行類似上述計(jì)算昂貴的代碼试和,瀏覽器往往會卡頓很長時(shí)間,將這些操作分隔纫普,定期讓代碼中斷阅悍,并記錄中斷的地方,間隔一定時(shí)間再調(diào)度下一階段。
var row = 20000;
var divede = 10; // 分隔的數(shù)量
var cur = row / divede; // 每次執(zhí)行的次數(shù)
var now = 0; // 執(zhí)行的階段
var tbody = document.getElementsByTagName("tbody")[0];
setTimeout(function goNow() {
var go = cur * now; // 記錄上次中斷結(jié)束的地方
for(var i=0;i<cur;i++){
var tr = document.createElement("tr");
for(var j=0;j<6;j++){
var td = document.createElement("td");
td.appendChild(document.createTextNode((i+go)+","+j+","+now));
tr.appendChild(td);
}
tbody.appendChild(tr);
}
now++; // 調(diào)度下一階段
if(now<divede){
setTimeout(goNow,0);
}
},0);
中央定時(shí)器控制
var timers = { // 定義中央定時(shí)器控制對象
timerID:0, // 當(dāng)前正在執(zhí)行的定時(shí)器节视,為0則表示沒有定時(shí)器在執(zhí)行
timers: [], // 保存定時(shí)器需要執(zhí)行的所有回調(diào)函數(shù)
add:function (fn) { // 定義add方法拳锚,添加回調(diào)函數(shù)
this.timers.push(fn);
},
start:function (time) { // 執(zhí)行定時(shí)器
if(this.timerID) return; // 沒有定時(shí)器時(shí),執(zhí)行一個(gè)即時(shí)函數(shù)來開啟中央定時(shí)器
(function runNext() { // 執(zhí)行當(dāng)前定時(shí)器
if(timers.timers.length>0){ // 遍歷所有回調(diào)函數(shù)進(jìn)行執(zhí)行
for(var i=0;i<timers.timers.length;i++){
if(timers.timers[i]()===false){ // 當(dāng)某個(gè)回調(diào)函數(shù)不需要在執(zhí)行時(shí)寻行,將其刪除
timers.timers.splice(i,1);
i--;
}
}
timers.timerID = setTimeout(runNext,time); // 等待time時(shí)間再次調(diào)用定時(shí)器
}
})();
},
stop:function () { // 清除定時(shí)器
clearTimeout(this.timerID);
this.timerID =0;
}
};
// 測試中央控制器
var box = document.getElementById("box"),x=0,y=0;
timers.add(function () {
box.style.left=x+"px";
if((x+=1)>200)return false;
});
timers.add(function () {
box.style.top=y+"px";
if((y+=1)>200)return false;
});
timers.start(10);