Javascript非同步編程之setTimeout與setInterval詳解

來源:互聯網
上載者:User

標籤:地方   事件處理器   調用   輸出   第一個   需求   研究   index   strong   

 

http://www.cnblogs.com/tugenhua0707/

在談到非同步編程時,本人最主要會從以下三個方面來總結非同步編程( 注意: 特別解釋:是總結,本人也是菜鳥,所以總結不好的,請各位大牛多多原諒!)

1. setTimeout與setInterval詳細分析基本原理。

  

接下來這篇部落格會總結setTimeout和setInterval基本點,對於上面三點會分三篇部落格分別來總結,對於知道上面三點的人,但是又不是非常瞭解全面知識點的碼農來說,沒有關係的,我們可以慢慢來學習,來理解,或者我總結不全面的或者不好地方可以留言,學習本來就是要互動,才有提高。當然對於那些知識大牛來說,也可以看下,如果我總結不好的話,也可以提提意見,我也可以多學習學習下!

在研究setTimeout與setInterval之前,我們可以先來看看一個小小的demo,其實總結與研究就是要多做demo,因為有的事情我們看起來很簡單,真正做起來的時候不是那麼一回事。比如如下:

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

setTimeout(function(){

console.log(i);

},100);

}

如果javascript語言不是很熟悉的話,很多人會理所當然的認為for迴圈會分別列印出1,2,3. 但是事實不是這樣的,會輸出3次4. 要理解為什麼會列印三次4,我們先來理解setTimeout這個函數吧,很多人會認為上面的setTimeout的意思是這樣的,在100毫秒後執行setTimeout的回呼函數,其實這樣的理解是有誤的,其實setTimeout與setInterval真正的含義如下:

  1. setTimeout:在指定的毫秒數後,將定時任務處理的函數添加到執行隊列的隊尾。
  2. setInterval:按照指定的周期(以毫秒數計時),將定時任務處理函數添加到執行隊列的隊尾。

setTimeout與setInterval且都是非同步,所以我們現在可以來理解下上面迴圈為什麼一直都是4呢?其實調用setTimeout時候,會有一個延時事件排入隊列,然後setTimeout調用之後的那行代碼運行,接著是再下一行代碼,直到再也沒有任何代碼了,javascript虛擬機器才會問,隊列裡還有嗎?如果隊列中至少有一個事件適合於觸發,比如上面的setTimeout函數,則會調用setTimeout那個函數。所以上面的代碼先for迴圈,迴圈結束,而 i === 4一直遞增,直到不再滿足i<=3為止。所以就列印了3個4.

我們再來看看下面的函數,如下:

setTimeout(function(){

console.log("列印我,我是非同步執行的");

},100);

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

運行結果是:先列印出 “我是新來的,我先執行”這句代碼,接著列印”列印我,我是非同步執行的”代碼。

二:理解javascript線程。

Javascript引擎是單線程啟動並執行,瀏覽器無論在什麼時候都只且只有一個線程在啟動並執行。

那麼單線程是如何配合瀏覽器核心處理這些定時器和相應瀏覽器事件呢?

瀏覽器核心允許多個線程非同步執行,這些線程在核心控制下相互配合以保持同步,比如一個瀏覽器至少有3個以上的線程,有:javascript引擎線程,介面渲染線程,瀏覽器事件觸發線程,除這些以外,也有一些執行完的線程,比如http請求線程,這些非同步線程都會產生不同的非同步事件。

介面渲染線程:

該線程負責渲染瀏覽器HTML介面元素,當介面需要重繪或由於某種操作引發迴流(reflow),該線程就會執行,該線程與javascript引擎線程是互斥的,因為javascript引擎運行指令碼期間,瀏覽器渲染線程都是出於掛起狀態的,比如我們常見的是在頁面head標籤內不建議把JS放在頭部的原因,希望要把JS放在尾部或者使用非同步載入等操作。因此在指令碼中執行對介面進行更新操作,如動態添加節點或者刪除節點等更新會把這些事件放在隊列當中,等javascript引擎空閑時才有機會渲染出來。

瀏覽器事件觸發線程:

使用者單擊一個已附加有單擊事件處理器dom元素時,會有一個單擊事件排入隊列,但是該單擊事件處理器要等到當前所有正在啟動並執行代碼均已結束才會執行。

比如如下一個小demo,我們平時寫代碼時候,特別用原審javascript寫tab切換的時候,經常會碰到如下代碼,比如點擊一個li標籤,希望切換到對應的內容上來。如下點擊事件demo。我這裡使用jquery來示範下:

HTML代碼如下結構:

<li class="container">點擊我1</ li >

< li class="container">點擊我2</ li >

< li class="container">點擊我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

});

}

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

定時觸發線程:

這裡談到的定時計數器不是由javascript引擎計數的,因為javascript引擎是單線程的,如果處於堵塞狀態就計不了時的,它必須依賴外部計時並觸發定時,所以隊列中的定時事件也是非同步事件。

三:理解setTimeout與setInterval非同步事件:

Javascript最基礎的非同步函數是setTimeout與setInterval,setTimeout會在一定的時間後執行相應的函數,它接受一個回呼函數和一個毫秒時間,比如如下:

console.log( "a" );

setTimeout(function() {

console.log( "c" )

}, 500 );

setTimeout(function() {

console.log( "d" )

}, 500 );

setTimeout(function() {

console.log( "e" )

}, 500 );

console.log( "b" );

控制台先輸出“a”、“b”,大約500毫秒後,再看到“c”、“d”、“e”。

但是如果我把第一個setTimeout的延時時間改大一點或者改為600毫秒,那麼列印出來就分別是a,b,d,e,c了。你可能聽過事件迴圈這個詞,它是用於描述隊列的工作方式的。當非同步函數執行時,回呼函數就會被壓入這個隊列裡面,javascript引擎直到非同步函數執行完,才會開始出來這個事件迴圈,這意味著javascript也並不是多線程的,事件迴圈是一個先進先出的(FIFO)隊列,這說明回調是按照他們被排入佇列的順序執行的(在相同的情況下。),但是如果延遲時間不一樣的話,那麼就不會了,就像上面的列子把定時毫秒數改大點輸出來的就不一樣了。

四:非同步函數的類型

在Javascript環境中提供的非同步函數分為2大類:I/O函數和計時函數。

 

我們都知道建立nodeJS不是為了在伺服器上運行javascript,而是因為javascript語言可以完美的實現非堵塞式的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;

alert(xhr.responseText);

};

alert("Ajax還沒完成呢?");

運行結果後先執行”Ajax還沒完成呢?”,後執行onreadystatechange的回呼函數。在ajax函數中先執行send方法後,再綁定事件呢,而不是先綁定事件,再send呢?

其實xhr對象使用了其他線程,這裡涉及到一些跨線程通訊的問題,跨線程訪問資料時需要使用委託,否則會發生資料衝突,所謂委託其實就是一個線程向另一個線程發送訊息,但是xhr線程想要觸發主線程xhr對象的onreadystatechange事件就需要委託,而主線程目前是忙碌狀態,它正在出理初始化訊息,只有等到初始化訊息空閑後才會執行子線程的委託處理,而初始化訊息空閑時就意味著onreadystatechange事件被綁定上了,所以後面的代碼執行會永遠比xhr線程執行要快。所以先會執行後面的alert對話方塊,再執行onreadystatechange事件。當然ajax請求第三個參數我們可以設定成false,同步請求,一般情況下還是非同步請求好,但是為了處理一些特殊的需求,也可以設定同步請求(注意:同步請求會堵塞瀏覽器載入,所以如果請求的資料很大的時候,還是考慮非同步請求。),比如一些常見的需求,發送ajax請求後,要開啟一個新視窗這樣的一個需求,我們都知道如果是非同步請求chrome和firefox直接會被攔截掉,但是如果我設定了同步請求就可以實現發送ajax請求後,再開啟一個新視窗了。

 

我們已經看到,非同步函數非常適用於I/O操作,但是我們現在想讓一個函數在將來某時刻來運行或者一個動畫函數在將來某個時候來執行動畫效果,這時候我們會想到javascript中的setTimeout與setInterval函數了。但是setTimeout與setInterval有如下缺陷:

  1. 當同一個javascript進程啟動並執行代碼時候,任何javascript計時函數都無法使代碼運行起來,如下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,反正結果一直是1s以上,也就是說後面的函數如果執行時間非常長的話,那麼setTimeout代碼永遠不會執行。

2. setInterval根據HTML規範可知:在一個小時之內會延遲 4-5ms這麼一個延遲。也就是說使用這個計時不是非常精確

Javascript非同步編程之setTimeout與setInterval詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.