異步一直是前端開發裏最讓人頭疼的一個難點,接下來的幾篇文章,將圍繞這個話題展開。html
衆所周知,JS最初的目的是用於處理瀏覽器的用戶交互和操做DOM,所以,若是JS設計成容許同時存在2個以上的線程,就會出現如下這種問題:前端
2個線程同時操做了同一個DOM節點(a線程要編輯該節點,而b線程刪除該節點),那麼此時瀏覽器將沒法處理,由於沒法判斷以哪一個線程爲基準。所以,JS只能是單線程。(Web Worker API雖然提供了多線程,但只是純粹基於使用多核cpu的計算能力,其建立的子線程嚴格受控,不影響JS單線程的設計實質)
,單線程的設計就意味着,任務以排隊的方式依此執行。ajax
基於單線程設計,不可避免的遇到一個情形:某些任務須要的時間很長,但不是由於任務自己太過複雜,難以處理,而是輸入輸出太慢(例如Ajax獲取數據)。而在等待輸入輸出的過程當中,CPU是閒置的,爲了充分利用資源,這一類任務被設計成容許暫時掛起,等到有告終果再執行的任務。segmentfault
如今有兩種任務了:同步任務和異步任務api
接下來介紹JS的處理機制。promise
首先看來自MDN的一張圖:瀏覽器
棧(stack),函數調用堆棧。
看這個例子:多線程
function a(){ console.log('a') } function b(){ console.log('from') a() // 這裏調用了函數a } b()
在Chrome中運行,而且單步調試,能夠看到如下步驟:閉包
b()
時,函數b進棧(如圖1)b
中調用函數a
時,a
繼續進棧(如圖2)(這部份內容實際上對應着以前介紹閉包時,函數做用域鏈的生成部分,傳送門)dom
堆(heap),內存區,用於存儲對象。(這個目前不是很重要先不用管)
隊列(queue),待處理消息隊列, 每個消息都關聯着一個用以處理這個消息的函數。
常見示例:
handleClick
函數,那麼,當用戶觸發點擊按鈕的動做時,會有一個待處理消息進入queue,關聯的函數爲handleClick
。總體的執行過程以下(如圖):
上述過程循環執行,因此稱爲事件循環(Event Loop)
// 簡單的例子 var req = new XMLHttpRequest(); req.open('GET', url); req.onload = function (){}; //指定回調函數, 這是一個異步任務,會被先提交到異步處理的api,等有告終果纔會添加到消息隊列 req.send();
補充說明如下,任務隊列分紅2類:
他們的區別下次講解Promise時再說明(挖個坑)
上述Event Loop模型中,消息隊列的新消息來源,除了有dom事件操做,ajax請求等,也多是定時任務,也就是由setTimeout
建立的任務。這個函數你們確定不陌生,可是也可能未必真的足夠熟悉~。
setTimeout
接受兩個參數:
如今看下如下2個例子:
//示例1 console.log(1); setTimeout(function(){console.log(2);},1000); console.log(3); // 輸出結果 1 3 2 ,由於setTimeout指定了裏面的函數要推遲1000毫秒纔會執行
這個例子說明了setTimeout的基本做用,比較簡單很少說。
//示例2 const s = new Date().getSeconds(); //獲取當前的秒數 setTimeout(function() { // 輸出 "2",表示回調函數並無在 500 毫秒以後當即執行 console.log("Ran after " + (new Date().getSeconds() - s) + " seconds"); }, 500); while(true) {//這個循環含義就是,至少要過2s,當前主線程任務才執行完畢 if(new Date().getSeconds() - s >= 2) { console.log("Good, looped for 2 seconds"); break; } } //實際輸出 Good, looped for 2 seconds eventloop.html:15 Ran after 2 seconds
這個例子,首先使用setTimeout
指定了一個500毫秒後執行的回調函數,而後使用while
循環故意讓當前運行超過2秒鐘,根據上文的流程圖可知:
其實在第500毫秒時,這個消息已經被添加到消息隊列,可是因爲當前的主線程並無執行完,調用棧還沒有清空,因此在500毫秒不會執行setTimeout
指定的回調函數。實際上,即便把上述代碼中的500
改爲0
,結果也是同樣的。
簡而言之,setTimeout(fn,x毫秒)
的x只是指定了fn被執行的最小等待時間,息具體能在多少時間以後執行,取決於現有調用棧函數的執行進度,以及消息隊列中前面的任務執行進度。
本文介紹了Event Loop模型過程以及常見的任務隊列的幾種任務隊列消息來源,這是JS異步話題的基礎篇。
參考文獻:
MDN-EventLoop
JavaScript 運行機制詳解:再談Event Loop
慣例:若是內容有錯誤的地方歡迎指出(以爲看着不理解不舒服想吐槽也徹底沒問題);若是有幫助,歡迎點贊和收藏,轉載請徵得贊成後著明出處,若是有問題也歡迎私信交流,主頁有郵箱地址