Javascript異步編程之setTimeout與setInterval詳解分析

一. setTimeout與setInterval詳細(xì)分析基本原理。

接下來這篇博客會(huì)總結(jié)setTimeout和setInterval基本點(diǎn),對于上面三點(diǎn)會(huì)分三篇博客分別來總結(jié),對于知道上面三點(diǎn)的人,但是又不是非常了解全面知識點(diǎn)的碼農(nóng)來說,沒有關(guān)系的,我們可以慢慢來學(xué)習(xí),來理解,或者我總結(jié)不全面的或者不好地方可以留言,學(xué)習(xí)本來就是要互動(dòng),才有提高。當(dāng)然對于那些知識大牛來說,也可以看下,如果我總結(jié)不好的話,也可以提提意見,我也可以多學(xué)習(xí)學(xué)習(xí)下!

在研究setTimeout與setInterval之前,我們可以先來看看一個(gè)小小的demo,其實(shí)總結(jié)與研究就是要多做demo,因?yàn)橛械氖虑槲覀兛雌饋砗芎唵?,真正做起來的時(shí)候不是那么一回事。比如如下:

for(var i = 1; i <= 3; i++) {

setTimeout(function(){

console.log(i);

},100);

}```

如果javascript語言不是很熟悉的話,很多人會(huì)理所當(dāng)然的認(rèn)為for循環(huán)會(huì)分別打印出1,2,3. 但是事實(shí)不是這樣的,會(huì)輸出3次4. 要理解為什么會(huì)打印三次4,我們先來理解setTimeout這個(gè)函數(shù)吧,很多人會(huì)認(rèn)為上面的setTimeout的意思是這樣的,在100毫秒后執(zhí)行setTimeout的回調(diào)函數(shù),其實(shí)這樣的理解是有誤的,其實(shí)setTimeout與setInterval真正的含義如下:

setTimeout:在指定的毫秒數(shù)后,將定時(shí)任務(wù)處理的函數(shù)添加到執(zhí)行隊(duì)列的隊(duì)尾。
setInterval:按照指定的周期(以毫秒數(shù)計(jì)時(shí)),將定時(shí)任務(wù)處理函數(shù)添加到執(zhí)行隊(duì)列的隊(duì)尾。
setTimeout與setInterval且都是異步的,所以我們現(xiàn)在可以來理解下上面循環(huán)為什么一直都是4呢?其實(shí)調(diào)用setTimeout時(shí)候,會(huì)有一個(gè)延時(shí)事件排入隊(duì)列,然后setTimeout調(diào)用之后的那行代碼運(yùn)行,接著是再下一行代碼,直到再也沒有任何代碼了,javascript虛擬機(jī)才會(huì)問,隊(duì)列里還有嗎?如果隊(duì)列中至少有一個(gè)事件適合于觸發(fā),比如上面的setTimeout函數(shù),則會(huì)調(diào)用setTimeout那個(gè)函數(shù)。所以上面的代碼先for循環(huán),循環(huán)結(jié)束,而 i === 4一直遞增,直到不再滿足i<=3為止。所以就打印了3個(gè)4.

我們再來看看下面的函數(shù),如下:

setTimeout(function(){

console.log("打印我,我是異步執(zhí)行的");

},100);```

console.log("我是新來的,我要先執(zhí)行");

運(yùn)行結(jié)果是:先打印出 “我是新來的,我先執(zhí)行”這句代碼,接著打印”打印我,我是異步執(zhí)行的”代碼。

二:理解javascript線程。

Javascript引擎是單線程運(yùn)行的,瀏覽器無論在什么時(shí)候都只且只有一個(gè)線程在運(yùn)行的。

那么單線程是如何配合瀏覽器內(nèi)核處理這些定時(shí)器和相應(yīng)瀏覽器事件呢?

瀏覽器內(nèi)核允許多個(gè)線程異步執(zhí)行,這些線程在內(nèi)核控制下相互配合以保持同步,比如一個(gè)瀏覽器至少有3個(gè)以上的線程,有:javascript引擎線程,界面渲染線程,瀏覽器事件觸發(fā)線程,除這些以外,也有一些執(zhí)行完的線程,比如http請求線程,這些異步線程都會(huì)產(chǎn)生不同的異步的事件。

界面渲染線程:

該線程負(fù)責(zé)渲染瀏覽器HTML界面元素,當(dāng)界面需要重繪或由于某種操作引發(fā)回流(reflow),該線程就會(huì)執(zhí)行,該線程與javascript引擎線程是互斥的,因?yàn)閖avascript引擎運(yùn)行腳本期間,瀏覽器渲染線程都是出于掛起狀態(tài)的,比如我們常見的是在頁面head標(biāo)簽內(nèi)不建議把JS放在頭部的原因,希望要把JS放在尾部或者使用異步加載等操作。因此在腳本中執(zhí)行對界面進(jìn)行更新操作,如動(dòng)態(tài)添加節(jié)點(diǎn)或者刪除節(jié)點(diǎn)等更新會(huì)把這些事件放在隊(duì)列當(dāng)中,等javascript引擎空閑時(shí)才有機(jī)會(huì)渲染出來。

瀏覽器事件觸發(fā)線程:

用戶單擊一個(gè)已附加有單擊事件處理器dom元素時(shí),會(huì)有一個(gè)單擊事件排入隊(duì)列,但是該單擊事件處理器要等到當(dāng)前所有正在運(yùn)行的代碼均已結(jié)束才會(huì)執(zhí)行。

比如如下一個(gè)小demo,我們平時(shí)寫代碼時(shí)候,特別用原審javascript寫tab切換的時(shí)候,經(jīng)常會(huì)碰到如下代碼,比如點(diǎn)擊一個(gè)li標(biāo)簽,希望切換到對應(yīng)的內(nèi)容上來。如下點(diǎn)擊事件demo。我這里使用jquery來演示下:

HTML代碼如下結(jié)構(gòu):

< li class="container">點(diǎn)擊我1</ li >
< li class="container">點(diǎn)擊我2</ li >

< li class="container">點(diǎn)擊我3</ li >```

JS如下:

var lists = $(".container");

for(var i = 0, ilen = lists.length; i < ilen; i++) {

$(lists[i]).bind('click',function(){

console.log(i); // 打印3

});

}```

上面的代碼點(diǎn)擊一下,打印出3(不是0,1,2),原理還是和上面一樣。

定時(shí)觸發(fā)線程:

這里談到的定時(shí)計(jì)數(shù)器不是由javascript引擎計(jì)數(shù)的,因?yàn)閖avascript引擎是單線程的,如果處于堵塞狀態(tài)就計(jì)不了時(shí)的,它必須依賴外部計(jì)時(shí)并觸發(fā)定時(shí),所以隊(duì)列中的定時(shí)事件也是異步事件。

三:理解setTimeout與setInterval異步事件:

Javascript最基礎(chǔ)的異步函數(shù)是setTimeout與setInterval,setTimeout會(huì)在一定的時(shí)間后執(zhí)行相應(yīng)的函數(shù),它接受一個(gè)回調(diào)函數(shù)和一個(gè)毫秒時(shí)間,比如如下:

console.log( "a" );

setTimeout(function() {

console.log( "c" )

}, 500 );

setTimeout(function() {

console.log( "d" )

}, 500 );

setTimeout(function() {

console.log( "e" )

}, 500 );

console.log( "b" );```

控制臺(tái)先輸出“a”、“b”,大約500毫秒后,再看到“c”、“d”、“e”。

但是如果我把第一個(gè)setTimeout的延時(shí)時(shí)間改大一點(diǎn)或者改為600毫秒,那么打印出來就分別是a,b,d,e,c了。你可能聽過事件循環(huán)這個(gè)詞,它是用于描述隊(duì)列的工作方式的。當(dāng)異步函數(shù)執(zhí)行時(shí),回調(diào)函數(shù)就會(huì)被壓入這個(gè)隊(duì)列里面,,但是如果延遲時(shí)間不一樣的話,那么就不會(huì)了,就像上面的列子把定時(shí)毫秒數(shù)改大點(diǎn)輸出來的就不一樣了。

#四:異步函數(shù)的類型

在Javascript環(huán)境中提供的異步函數(shù)分為2大類:I/O函數(shù)和計(jì)時(shí)函數(shù)。

我們都知道創(chuàng)建nodeJS不是為了在服務(wù)器上運(yùn)行javascript,而是因?yàn)閖avascript語言可以完美的實(shí)現(xiàn)非堵塞式的I/O。比如典型的ajax請求,如下代碼:

var url = "http://localhost/setTimeout/index2.php";

var xhr=new XMLHttpRequest;

xhr.open("GET","http://localhost/setTimeout/index2.php",true);

xhr.send();

xhr.onreadystatechange=function(){

if(xhr.readyState<4)return;

;

};

;


運(yùn)行結(jié)果后先執(zhí)行”Ajax還沒完成呢?”,后執(zhí)行onreadystatechange的回調(diào)函數(shù)。在ajax函數(shù)中先執(zhí)行send方法后,再綁定事件呢,而不是先綁定事件,再send呢?

其實(shí)xhr對象使用了其他線程,這里涉及到一些跨線程通信的問題,跨線程訪問數(shù)據(jù)時(shí)需要使用委托,否則會(huì)發(fā)生數(shù)據(jù)沖突,所謂委托其實(shí)就是一個(gè)線程向另一個(gè)線程發(fā)送消息,但是xhr線程想要觸發(fā)主線程xhr對象的onreadystatechange事件就需要委托,而主線程目前是忙碌狀態(tài),它正在出理初始化消息,只有等到初始化消息空閑后才會(huì)執(zhí)行子線程的委托處理,而初始化消息空閑時(shí)就意味著onreadystatechange事件被綁定上了,所以后面的代碼執(zhí)行會(huì)永遠(yuǎn)比xhr線程執(zhí)行要快。所以先會(huì)執(zhí)行后面的alert對話框,再執(zhí)行onreadystatechange事件。當(dāng)然ajax請求第三個(gè)參數(shù)我們可以設(shè)置成false,同步請求,一般情況下還是異步請求好,但是為了處理一些特殊的需求,也可以設(shè)置同步請求(注意:同步請求會(huì)堵塞瀏覽器加載,所以如果請求的數(shù)據(jù)很大的時(shí)候,還是考慮異步請求。),比如一些常見的需求,發(fā)送ajax請求后,要打開一個(gè)新窗口這樣的一個(gè)需求,我們都知道如果是異步請求chrome和firefox直接會(huì)被攔截掉,但是如果我設(shè)置了同步請求就可以實(shí)現(xiàn)發(fā)送ajax請求后,再打開一個(gè)新窗口了。

我們已經(jīng)看到,異步函數(shù)非常適用于I/O操作,但是我們現(xiàn)在想讓一個(gè)函數(shù)在將來某時(shí)刻來運(yùn)行或者一個(gè)動(dòng)畫函數(shù)在將來某個(gè)時(shí)候來執(zhí)行動(dòng)畫效果,這時(shí)候我們會(huì)想到j(luò)avascript中的setTimeout與setInterval函數(shù)了。但是setTimeout與setInterval有如下缺陷:

當(dāng)同一個(gè)javascript進(jìn)程運(yùn)行的代碼時(shí)候,任何javascript計(jì)時(shí)函數(shù)都無法使代碼運(yùn)行起來,如下demo測試:

var start = new Date;

stTimeout(function(){

var end = new Date;

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

},500);

while(new Date - start < 1000) {

}```

想打印出上面的console.log, 在瀏覽器一直刷新看到,第一次1020ms,第二次1029ms,反正結(jié)果一直是1s以上,也就是說后面的函數(shù)如果執(zhí)行時(shí)間非常長的話,那么setTimeout代碼永遠(yuǎn)不會(huì)執(zhí)行。

  1. setInterval根據(jù)HTML規(guī)范可知:在一個(gè)小時(shí)之內(nèi)會(huì)延遲 4-5ms這么一個(gè)延遲。也就是說使用這個(gè)計(jì)時(shí)不是非常精確。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容