JavaScript可否多線程? 深入理解JavaScript定時機制
當前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
JavaScript的setTimeout與setInterval是兩個很容易欺騙別人感情的方法,因為我們開始常常以為調(diào)用了就會按既定的方 式執(zhí)行, 我想不少人都深有同感, 例如
認為setTimeout中的問候方法會立即被執(zhí)行,因為這并不是憑空而說,而是JavaScript API文檔明確定義第二個參數(shù)意義為隔多少毫秒后,回調(diào)方法就會被執(zhí)行. 這里設(shè)成0毫秒,理所當然就立即被執(zhí)行了. 但隨著JavaScript應(yīng)用開發(fā)經(jīng)驗不斷的增加和豐富,有一天你發(fā)現(xiàn)了一段怪異的代碼而百思不得其解:
既然是0毫秒后執(zhí)行,那么還用setTimeout干什么, 此刻, 堅定的信念已開始動搖. 直到最后某一天 , 你不小心寫了一段糟糕的代碼:
第一行代碼進入了死循環(huán),但不久你就會發(fā)現(xiàn),第二,第三行并不是預料中的事情,alert問候未見出現(xiàn),callbacKFunction也杳無音 訊! 這時你徹底迷惘了,這種情景是難以接受的,因為改變長久以來既定的認知去接受新思想的過程是痛苦的,但情事實擺在眼前,對JavaScript真理 的探求并不會因為痛苦而停止,下面讓我們來展開JavaScript線程和定時器探索之旅! 拔開云霧見月明 出現(xiàn)上面所有誤區(qū)的最主要一個原因是:潛意識中認為,JavaScript引擎有多個線程在執(zhí)行,JavaScript的定時器回調(diào)函數(shù)是異步執(zhí)行 的. 而事實上的,JavaScript使用了障眼法,在多數(shù)時候騙過了我們的眼睛,這里背光得澄清一個事實:
JavaScript引擎用單線程運行也是有意義的,單線程不必理會線程同步這些復雜的問題,問題得到簡化. 那么單線程的JavaScript引擎是怎么配合瀏覽器內(nèi)核處理這些定時器和響應(yīng)瀏覽器事件的呢? 瀏覽器內(nèi)核實現(xiàn)允許多個線程異步執(zhí)行,這些線程在內(nèi)核制控下相互配合以保持同步.假如某一瀏覽器內(nèi)核的實現(xiàn)至少有三個常駐線 程:javascript引擎線程,界面渲染線程,瀏覽器事件觸發(fā)線程,除些以外,也有一些執(zhí)行完就終止的線程,如Http請求線程,這些異步線程都會產(chǎn) 生不同的異步事件,下面通過一個圖來闡明單線程的JavaScript引擎與另外那些線程是怎樣互動通信的.雖然每個瀏覽器內(nèi)核實現(xiàn)細節(jié)不同,但這其中的 調(diào)用原理都是大同小異. 上圖t1-t2..tn表示不同的時間點,tn下面對應(yīng)的小方塊代表該時間點的任務(wù),假設(shè)現(xiàn)在是t1時刻,引擎運行在t1對應(yīng)的任務(wù)方塊代碼內(nèi),在這個時間點內(nèi),我們來描述一下瀏覽器內(nèi)核其它線程的狀態(tài). t1時刻: GUI渲染線程: 該線程負責渲染瀏覽器界面HTML元素,當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行.本文雖然重點解釋JavaScript定時機制,但這時有必要說說渲染線程,因為該線程與JavaScript引擎線程是互斥的,這容易理解,因為 JavaScript腳本是可操縱DOM元素,在修改這些元素屬性同時渲染界面,那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了. 在JavaScript引擎運行腳本期間,瀏覽器渲染線程都是處于掛起狀態(tài)的,也就是說被”凍結(jié)”了. 所以,在腳本中執(zhí)行對界面進行更新操作,如添加結(jié)點,刪除結(jié)點或改變結(jié)點的外觀等更新并不會立即體現(xiàn)出來,這些操作將保存在一個隊列中,待 JavaScript引擎空閑時才有機會渲染出來. GUI事件觸發(fā)線程: JavaScript腳本的執(zhí)行不影響html元素事件的觸發(fā),在t1時間段內(nèi),首先是用戶點擊了一個鼠標鍵,點擊被瀏覽器事件觸發(fā)線程捕捉后形成一個鼠標點擊事件,由圖可知,對于JavaScript引擎線程來說,這事件是由其它線程異步傳到任務(wù)隊列尾的,由于引擎正在處理t1時的任務(wù),這個鼠標點擊事件正在等待處理. 定時觸發(fā)線程: 注意這里的瀏覽器模型定時計數(shù)器并不是由JavaScript引擎計數(shù)的,因為JavaScript引擎是單線程的,如果處于阻塞線程狀態(tài)就計不了時,它必須依賴外部來計時并觸發(fā)定時,所以隊列中的定時事件也是異步事件. 由圖可知,在這t1的時間段內(nèi),繼鼠標點擊事件觸發(fā)后,先前已設(shè)置的setTimeout定時也到達了,此刻對JavaScript引擎來說,定時觸發(fā)線程產(chǎn)生了一個異步定時事件并放到任務(wù)隊列中, 該事件被排到點擊事件回調(diào)之后,等待處理. 可見,假如時間段t1非常長,遠大于setInterval的定時間隔,那么定時觸發(fā)線程就會源源不斷的產(chǎn)生異步定時事件并放到任務(wù)隊列尾而不管它們是否已被處理,但一旦t1和最先的定時事件前面的任務(wù)已處理完,這些排列中的定時事件就依次不間斷的被執(zhí)行,這是因為,對于JavaScript引擎來說,在處理隊列中的各任務(wù)處理方式都是一樣的,只是處理的次序不同而已. t1過后,也就是說當前處理的任務(wù)已返回,JavaScript引擎會檢查任務(wù)隊列,發(fā)現(xiàn)當前隊列非空,就取出t2下面對應(yīng)的任務(wù)執(zhí)行,其它時間依此類推,由此看來: 如果隊列非空,引擎就從隊列頭取出一個任務(wù),直到該任務(wù)處理完,即返回后引擎接著運行下一個任務(wù),在任務(wù)沒返回前隊列中的其它任務(wù)是沒法被執(zhí)行的. 相信您現(xiàn)在已經(jīng)很清楚JavaScript是否可多線程,也了解理解JavaScript定時器運行機制了,下面我們來對一些案例進行分析: 案例1:setTimeout與setInterval setTimeout(function(){ setInterval(function(){ 這兩段代碼看一起效果一樣,其實非也,第一段中回調(diào)函數(shù)內(nèi)的setTimeout是JavaScript引擎執(zhí)行后再設(shè)置新的setTimeout 定時, 假定上一個回調(diào)處理完到下一個回調(diào)開始處理為一個時間間隔,理論兩個setTimeout回調(diào)執(zhí)行時間間隔>=10ms .第二段自setInterval設(shè)置定時后,定時觸發(fā)線程就會源源不斷的每隔十秒產(chǎn)生異步定時事件并放到任務(wù)隊列尾,理論上兩個setInterval 回調(diào)執(zhí)行時間間隔<=10. 案例2:ajax異步請求是否真的異步? 很多同學朋友搞不清楚,既然說JavaScript是單線程運行的,那么XMLHttpRequest在連接后是否真的異步? 該文章在 2010/8/13 23:01:32 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |