摘要:的單線程,與它的用途有關。事件循環事件循環是指主線程重復從消息隊列中取消息執行的過程。到此為止,就完成了工作線程對主線程的通知,回調函數也就得到了執行。
一. 區分進程和線程
很多新手是區分不清線程和進程的,沒有關系。這很正常。先看看下面這個形象的比喻:
進程是一個工廠,工廠有它的獨立資源-工廠之間相互獨立-線程是工廠中的工人,多個工人協作完成任務-工廠內有一個或多個工人-工人之間共享空間
如果是windows電腦中,可以打開任務管理器,可以看到有一個后臺進程列表。對,那里就是查看進程的地方,而且可以看到每個進程的內存資源信息以及cpu占有率。
所以,應該更容易理解了:進程是cpu資源分配的最小單位(系統會給它分配內存)
最后,再用較為官方的術語描述一遍:
進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
線程是cpu調度的最小單位(線程是建立在進程的基礎上的一次程序運行單位,一個進程中可以有多個線程)
提示:
不同進程之間也可以通信,不過代價較大
現在,一般通用的叫法:單線程與多線程,都是指在一個進程內的單和多。(所以核心還是得屬于一個進程才行)
二. 瀏覽器是多進程的理解了進程與線程了區別后,接下來對瀏覽器進行一定程度上的認識:(先看下簡化理解)
瀏覽器是多進程的
瀏覽器之所以能夠運行,是因為系統給它的進程分配了資源(cpu、內存)
簡單點理解,每打開一個Tab頁,就相當于創建了一個獨立的瀏覽器進程。
關于以上幾點的驗證,請再第一張圖:
圖中打開了Chrome瀏覽器的多個標簽頁,然后可以在Chrome的任務管理器中看到有多個進程(分別是每一個Tab頁面有一個獨立的進程,以及一個主進程)。
感興趣的可以自行嘗試下,如果再多打開一個Tab頁,進程正常會+1以上(不過,某些版本的ie卻是單進程的)
注意:在這里瀏覽器應該也有自己的優化機制,有時候打開多個tab頁后,可以在Chrome任務管理器中看到,有些進程被合并了(所以每一個Tab標簽對應一個進程并不一定是絕對的)
三、為什么JavaScript是單線程?JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。
JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?
所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。
為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。
四. 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響應后,也就沒必要通知主線程,從而也沒必要往消息隊列放消息。
用圖表示這個過程就是:
從上文中我們也可以得到這樣一個明顯的結論,就是:
異步過程的回調函數,一定不在當前這一輪事件循環中執行。事件循環進階:macrotask與microtask
一張圖展示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(也就是main1、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》)
上圖大致描述就是:
主線程運行時會產生執行棧,棧中的代碼調用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發條件后,如ajax請求完畢)
而棧中的代碼執行完畢,就會讀取事件隊列中的事件,去執行那些回調
如此循環
注意,總是要等待棧中的代碼執行完畢后才會去讀取事件隊列中的事件
五. 最后看到這里,應該對JS的運行機制有一定的理解了吧。
參考:
http://www.ruanyifeng.com/blo...
https://mp.weixin.qq.com/s/vI...
https://mp.weixin.qq.com/s?__...
https://mp.weixin.qq.com/s/k_...
我不是大神,也不是什么牛人,寫這個號的目的是為了記錄我自學 web全棧 的筆記。
對 全棧修煉 有興趣的朋友可以掃下方二維碼關注我的公眾號
我會不定期更新有價值的內容,長期運營。
關注公眾號并回復 福利 可領取免費學習資料,福利詳情請猛戳: Python、Java、Linux、Go、node、vue、react、javaScript
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96378.html
摘要:深入理解引擎的執行機制靈魂三問為什么是單線程的為什么需要異步單線程又是如何實現異步的呢中的中的說說首先請牢記點是單線程語言的是的執行機制。 深入理解JS引擎的執行機制 1.靈魂三問 : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實現異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說說setTimeout 首先,請牢記2...
摘要:事件循環當主線程中的任務執行完畢后,會從任務隊列中獲取任務一個個的放在棧中執行去執行,這個過程是循環不斷的,所以整個的這種運行機制又稱為事件循環。 寫在前面 說起javascript(以下簡稱js)這門語言,相信大家已經非常熟悉了,不管是前端開發還是后端開發幾乎無時無刻都要跟它打交道。雖說開發者每天幾乎都要操作js,但是你真的確定你掌握了js的運行機制嗎!下面我們就來聊聊這話題。 Ja...
摘要:調用棧是單線程編程語言,意味著它只有單一的調用棧。調用棧是一種數據結構,基本記錄了程序運行的位置。舉個例子,先來看如下所示的代碼當引擎開始執行這段代碼時,調用棧將是空的。這正是拋出異常時棧追蹤的構造過程這基本上就是異常拋出時調用棧的狀態。 原文 How JavaScript works: an overview of the engine, the runtime, and the c...
摘要:對于通常的特別是那些具備并行計算多線程背景知識的來講,的異步處理著實稱得上詭異。而這個詭異從結果上講,是由的單線程這個特性所導致的。的特性之一是單線程,也即是從頭到尾,都在同一根線程下運行。而這兩者的不同,便在于單線程和多線程上。 對于通常的developer(特別是那些具備并行計算/多線程背景知識的developer)來講,js的異步處理著實稱得上詭異。而這個詭異從結果上講,是由js...
閱讀 3743·2021-11-22 13:52
閱讀 3622·2019-12-27 12:20
閱讀 2395·2019-08-30 15:55
閱讀 2150·2019-08-30 15:44
閱讀 2267·2019-08-30 13:16
閱讀 582·2019-08-28 18:19
閱讀 1891·2019-08-26 11:58
閱讀 3445·2019-08-26 11:47