不少新手是區分不清線程和進程的,沒有關係。這很正常。先看看下面這個形象的比喻:html
進程是一個工廠,工廠有它的獨立資源-工廠之間相互獨立-線程是工廠中的工人,多個工人協做完成任務-工廠內有一個或多個工人-工人之間共享空間
若是是windows電腦中,能夠打開任務管理器,能夠看到有一個後臺進程列表。對,那裏就是查看進程的地方,並且能夠看到每一個進程的內存資源信息以及cpu佔有率。vue
因此,應該更容易理解了:進程是cpu資源分配的最小單位(系統會給它分配內存)java
最後,再用較爲官方的術語描述一遍:node
提示:react
理解了進程與線程了區別後,接下來對瀏覽器進行必定程度上的認識:(先看下簡化理解)web
關於以上幾點的驗證,請再第一張圖:ajax
圖中打開了Chrome瀏覽器的多個標籤頁,而後能夠在Chrome的任務管理器中看到有多個進程(分別是每個Tab頁面有一個獨立的進程,以及一個主進程)。segmentfault
感興趣的能夠自行嘗試下,若是再多打開一個Tab頁,進程正常會+1以上(不過,某些版本的ie倒是單進程的)
注意:在這裏瀏覽器應該也有本身的優化機制,有時候打開多個tab頁後,能夠在Chrome任務管理器中看到,有些進程被合併了(因此每個Tab標籤對應一個進程並不必定是絕對的)windows
JavaScript語言的一大特色就是單線程,也就是說,同一個時間只能作一件事。那麼,爲何JavaScript不能有多個線程呢?這樣能提升效率啊。api
JavaScript的單線程,與它的用途有關。做爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操做DOM。這決定了它只能是單線程,不然會帶來很複雜的同步問題。好比,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另外一個線程刪除了這個節點,這時瀏覽器應該以哪一個線程爲準?
因此,爲了不復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,未來也不會改變。
爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。
單線程就意味着,全部任務須要排隊,前一個任務結束,纔會執行後一個任務。若是前一個任務耗時很長,後一個任務就不得不一直等着。
js引擎執行異步代碼而不用等待,是因有爲有 消息隊列和事件循環。
消息隊列:消息隊列是一個先進先出的隊列,它裏面存放着各類消息。
事件循環:事件循環是指主線程重複從消息隊列中取消息、執行的過程。
實際上,主線程只會作一件事情,就是從消息隊列裏面取消息、執行消息,再取消息、再執行。當消息隊列爲空時,就會等待直到消息隊列變成非空。並且主線程只有在將當前的消息執行完成後,纔會去取下一個消息。這種機制就叫作事件循環機制,取一個消息並執行的過程叫作一次循環。
事件循環用代碼表示大概是這樣的:
while(true) { var message = queue.get(); execute(message); }
那麼,消息隊列中放的消息具體是什麼東西?消息的具體結構固然跟具體的實現有關,可是爲了簡單起見,咱們能夠認爲:
消息就是註冊異步任務時添加的回調函數。
再次以異步AJAX爲例,假設存在以下的代碼:
$.ajax('http://segmentfault.com', function(resp) { console.log('我是響應:', resp); }); // 其餘代碼 ... ... ...
主線程在發起AJAX請求後,會繼續執行其餘代碼。AJAX線程負責請求segmentfault.com,拿到響應後,它會把響應封裝成一個JavaScript對象,而後構造一條消息:
// 消息隊列中的消息就長這個樣子 var message = function () { callbackFn(response); }
其中的callbackFn就是前面代碼中獲得成功響應時的回調函數。
主線程在執行完當前循環中的全部代碼後,就會到消息隊列取出這條消息(也就是message函數),並執行它。到此爲止,就完成了工做線程對主線程的通知,回調函數也就獲得了執行。若是一開始主線程就沒有提供回調函數,AJAX線程在收到HTTP響應後,也就不必通知主線程,從而也不必往消息隊列放消息。
用圖表示這個過程就是:
從上文中咱們也能夠獲得這樣一個明顯的結論,就是:
異步過程的回調函數,必定不在當前這一輪事件循環中執行。
一張圖展現JavaScript中的事件循環:
一次事件循環:先運行macroTask隊列中的一個,而後運行microTask隊列中的全部任務。接着開始下一次循環(只是針對macroTask和microTask,一次完整的事件循環會比這個複雜的多)。
JS中分爲兩種任務類型:macrotask和microtask,在ECMAScript中,microtask稱爲jobs,macrotask可稱爲task
它們的定義?區別?簡單點能夠按以下理解:
macrotask(又稱之爲宏任務),能夠理解是每次執行棧執行的代碼就是一個宏任務(包括每次從事件隊列中獲取一個事件回調並放到執行棧中執行)
每個task會從頭至尾將這個任務執行完畢,不會執行其它
瀏覽器爲了可以使得JS內部task與DOM任務可以有序的執行,會在一個task執行結束後,在下一個 task 執行開始前,對頁面進行從新渲染
(task->渲染->task->...)
microtask(又稱爲微任務),能夠理解是在當前 task 執行結束後當即執行的任務
也就是說,在當前task任務後,下一個task以前,在渲染以前
因此它的響應速度相比setTimeout(setTimeout是task)會更快,由於無需等渲染
也就是說,在某一個macrotask執行完後,就會將在它執行期間產生的全部microtask都執行完畢(在渲染前)
分別很麼樣的場景會造成macrotask和microtask呢?
macroTask: 主代碼塊, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering(能夠看到,事件隊列中的每個事件都是一個macrotask)
microTask: process.nextTick, Promise, Object.observe, MutationObserver
補充:在node環境下,process.nextTick的優先級高於Promise,也就是能夠簡單理解爲:在宏任務結束後會先執行微任務隊列中的nextTickQueue部分,而後纔會執行微任務中的Promise部分。
另外,setImmediate則是規定:在下一次Event Loop(宏任務)時觸發(因此它是屬於優先級較高的宏任務),(Node.js文檔中稱,setImmediate指定的回調函數,老是排在setTimeout前面),因此setImmediate若是嵌套的話,是須要通過多個Loop才能完成的,而不會像process.nextTick同樣沒完沒了。
實踐:上代碼
咱們以setTimeout、process.nextTick、promise爲例直觀感覺下兩種任務隊列的運行方式。
console.log('main1'); process.nextTick(function() { console.log('process.nextTick1'); }); setTimeout(function() { console.log('setTimeout'); process.nextTick(function() { console.log('process.nextTick2'); }); }, 0); new Promise(function(resolve, reject) { console.log('promise'); resolve(); }).then(function() { console.log('promise then'); }); console.log('main2');
彆着急看答案,先以上面的理論本身想一想,運行結果會是啥?
最終結果是這樣的:
main1 promise main2 process.nextTick1 promise then setTimeout process.nextTick2
process.nextTick 和 promise then在 setTimeout 前面輸出,已經證實了macroTask和microTask的執行順序。可是有一點必需要指出的是。上面的圖容易給人一個錯覺,就是主進程的代碼執行以後,會先調用macroTask,再調用microTask,這樣在第一個循環裏必定是macroTask在前,microTask在後。
可是最終的實踐證實:在第一個循環裏,process.nextTick1和promise then這兩個microTask是在setTimeout這個macroTask裏以前輸出的,這是爲何呢?
由於主進程的代碼也屬於macroTask(這一點我比較疑惑的是主進程都是一些同步代碼,而macroTask和microTask包含的都是一些異步任務,爲啥主進程的代碼會被劃分爲macroTask,不過從實踐來看確實是這樣,並且也有理論支撐:【翻譯】Promises/A+規範)。
主進程這個macroTask(也就是main一、promise和main2)執行完了,天然會去執行process.nextTick1和promise then這兩個microTask。這是第一個循環。以後的setTimeout和process.nextTick2屬於第二個循環
別看上面那段代碼好像特別繞,把原理弄清楚了,都同樣 ~
requestAnimationFrame、Object.observe(已廢棄) 和 MutationObserver這三個任務的運行機制你們能夠從上面看到,不一樣的只是具體用法不一樣。重點說下UI rendering。在HTML規範:event-loop-processing-model裏敘述了一次事件循環的處理過程,在處理了macroTask和microTask以後,會進行一次Update the rendering,其中細節比較多,總的來講會進行一次UI的從新渲染。
這裏就直接引用一張圖片來協助理解:(參考自Philip Roberts的演講《Help, I’m stuck in an event-loop》)
上圖大體描述就是:
看到這裏,應該對JS的運行機制有必定的理解了吧。
參考:
我不是大神,也不是什麼牛人,寫這個號的目的是爲了記錄我自學 web全棧 的筆記。
對 全棧修煉 有興趣的朋友能夠掃下方二維碼關注個人公衆號
我會不按期更新有價值的內容,長期運營。
關注公衆號並回復 福利 可領取免費學習資料,福利詳情請猛戳: Python、Java、Linux、Go、node、vue、react、javaScript