JavaScript能否多線程? 深刻理解JavaScript定時機制

JavaScript的setTimeout與setInterval是兩個很容易欺騙別人感情的方法,由於咱們開始經常覺得調用了就會按既定的方式執行, 我想很多人都深有同感, 例如 javascript

setTimeout( function(){ alert(’你好!’); } , 0);
setInterval( callbackFunction , 100); php

認爲setTimeout中的問候方法會當即被執行,由於這並非憑空而說,而是JavaScript API文檔明肯定義第二個參數意義爲隔多少毫秒後,回調方法就會被執行. 這裏設成0毫秒,理所固然就當即被執行了.
同理對setInterval的callbackFunction方法每間隔100毫秒就當即被執行深信不疑! html

但隨着JavaScript應用開發經驗不斷的增長和豐富,有一天你發現了一段怪異的代碼而百思不得其解: java

div.onclick = function(){
setTimeout( function(){document.getElementById(’inputField’).focus();}, 0);
}; ajax

既然是0毫秒後執行,那麼還用setTimeout幹什麼, 此刻, 堅決的信念已開始動搖.

直到最後某一天 , 你不當心寫了一段糟糕的代碼: 瀏覽器

setTimeout( function(){ while(true){} } , 100);
setTimeout( function(){ alert(’你好!’); } , 200);
setInterval( callbackFunction , 200); 多線程

第一行代碼進入了死循環,但不久你就會發現,第二,第三行並非預料中的事情,alert問候未見出現,callbacKFunction也杳無音訊! 異步

這時你完全迷惘了,這種情景是難以接受的,由於改變長久以來既定的認知去接受新思想的過程是痛苦的,但情事實擺在眼前,對JavaScript真理的探求並不會由於痛苦而中止,下面讓咱們來展開JavaScript線程和定時器探索之旅! 函數

拔開雲霧見月明 spa

出現上面全部誤區的最主要一個緣由是:潛意識中認爲,JavaScript引擎有多個線程在執行,JavaScript的定時器回調函數是異步執行的.

而事實上的,JavaScript使用了障眼法,在多數時候騙過了咱們的眼睛,這裏背光得澄清一個事實:

JavaScript引擎是單線程運行的,瀏覽器不管在何時都只且只有一個線程在運行JavaScript程序.

JavaScript引擎用單線程運行也是有意義的,單線程沒必要理會線程同步這些複雜的問題,問題獲得簡化.

那麼單線程的JavaScript引擎是怎麼配合瀏覽器內核處理這些定時器和響應瀏覽器事件的呢?
下面結合瀏覽器內核處理方式簡單說明.

瀏覽器內核實現容許多個線程異步執行,這些線程在內核制控下相互配合以保持同步.假如某一瀏覽器內核的實現至少有三個常駐線程:javascript引擎線程,界面渲染線程,瀏覽器事件觸發線程,除些之外,也有一些執行完就終止的線程,如Http請求線程,這些異步線程都會產生不一樣的異步事件,下面經過一個圖來闡明單線程的JavaScript引擎與另外那些線程是怎樣互動通訊的.雖然每一個瀏覽器內核實現細節不一樣,但這其中的調用原理都是大同小異.


由圖可看出,瀏覽器中的JavaScript引擎是基於事件驅動的,這裏的事件可看做是瀏覽器派給它的各類任務,這些任務能夠源自 JavaScript引擎當前執行的代碼塊,如調用setTimeout添加一個任務,也可來自瀏覽器內核的其它線程,如界面元素鼠標點擊事件,定時觸發器時間到達通知,異步請求狀態變動通知等.從代碼角度看來任務實體就是各類回調函數,JavaScript引擎一直等待着任務隊列中任務的到來.因爲單線程關係,這些任務得進行排隊,一個接着一個被引擎處理.

上圖t1-t2..tn表示不一樣的時間點,tn下面對應的小方塊表明該時間點的任務,假設如今是t1時刻,引擎運行在t1對應的任務方塊代碼內,在這個時間點內,咱們來描述一下瀏覽器內核其它線程的狀態.

t1時刻:

GUI渲染線程:

該線程負責渲染瀏覽器界面HTML元素,當界面須要重繪(Repaint)或因爲某種操做引起迴流(reflow)時,該線程就會執行.本文雖然重點解釋JavaScript定時機制,但這時有必要說說渲染線程,由於該線程與JavaScript引擎線程是互斥的,這容易理解,由於 JavaScript腳本是可操縱DOM元素,在修改這些元素屬性同時渲染界面,那麼渲染線程先後得到的元素數據就可能不一致了.

在JavaScript引擎運行腳本期間,瀏覽器渲染線程都是處於掛起狀態的,也就是說被」凍結」了.

因此,在腳本中執行對界面進行更新操做,如添加結點,刪除結點或改變結點的外觀等更新並不會當即體現出來,這些操做將保存在一個隊列中,待JavaScript引擎空閒時纔有機會渲染出來.

GUI事件觸發線程:

JavaScript腳本的執行不影響html元素事件的觸發,在t1時間段內,首先是用戶點擊了一個鼠標鍵,點擊被瀏覽器事件觸發線程捕捉後造成一個鼠標點擊事件,由圖可知,對於JavaScript引擎線程來講,這事件是由其它線程異步傳到任務隊列尾的,因爲引擎正在處理t1時的任務,這個鼠標點擊事件正在等待處理.

定時觸發線程:

注意這裏的瀏覽器模型定時計數器並非由JavaScript引擎計數的,由於JavaScript引擎是單線程的,若是處於阻塞線程狀態就計不了時,它必須依賴外部來計時並觸發定時,因此隊列中的定時事件也是異步事件.

由圖可知,在這t1的時間段內,繼鼠標點擊事件觸發後,先前已設置的setTimeout定時也到達了,此刻對JavaScript引擎來講,定時觸發線程產生了一個異步定時事件並放到任務隊列中, 該事件被排到點擊事件回調以後,等待處理.
同理, 仍是在t1時間段內,接下來某個setInterval定時器也被添加了,因爲是間隔定時,在t1段內連續被觸發了兩次,這兩個事件被排到隊尾等待處理.

可見,假如時間段t1很是長,遠大於setInterval的定時間隔,那麼定時觸發線程就會源源不斷的產生異步定時事件並放到任務隊列尾而無論它們是否已被處理,但一旦t1和最早的定時事件前面的任務已處理完,這些排列中的定時事件就依次不間斷的被執行,這是由於,對於JavaScript引擎來講,在處理隊列中的各任務處理方式都是同樣的,只是處理的次序不一樣而已.

t1事後,也就是說當前處理的任務已返回,JavaScript引擎會檢查任務隊列,發現當前隊列非空,就取出t2下面對應的任務執行,其它時間依此類推,由此看來:

若是隊列非空,引擎就從隊列頭取出一個任務,直到該任務處理完,即返回後引擎接着運行下一個任務,在任務沒返回前隊列中的其它任務是無法被執行的.

相信您如今已經很清楚JavaScript是否可多線程,也瞭解理解JavaScript定時器運行機制了,下面咱們來對一些案例進行分析:

案例1:setTimeout與setInterval

setTimeout(function(){
   /* 代碼塊... */
   setTimeout(arguments.callee, 10);
}, 10);

setInterval(function(){
   /*代碼塊... */
 }, 10);

這兩段代碼看一塊兒效果同樣,其實非也,第一段中回調函數內的setTimeout是JavaScript引擎執行後再設置新的setTimeout 定時, 假定上一個回調處理完到下一個回調開始處理爲一個時間間隔,理論兩個setTimeout回調執行時間間隔>=10ms .第二段自setInterval設置定時後,定時觸發線程就會源源不斷的每隔十秒產生異步定時事件並放到任務隊列尾,理論上兩個setInterval 回調執行時間間隔<=10.

案例2:ajax異步請求是否真的異步?

不少同窗朋友搞不清楚,既然說JavaScript是單線程運行的,那麼XMLHttpRequest在鏈接後是否真的異步? 其實請求確實是異步的,不過這請求是由瀏覽器新開一個線程請求(參見上圖),當請求的狀態變動時,若是先前已設置回調,這異步線程就產生狀態變動事件放到 JavaScript引擎的處理隊列中等待處理,當任務被處理時,JavaScript引擎始終是單線程運行回調函數,具體點即仍是單線程運行 onreadystatechange所設置的函數.

相關文章
相關標籤/搜索