摘要:的單線程,與它的用途有關(guān)。事件循環(huán)事件循環(huán)是指主線程重復(fù)從消息隊(duì)列中取消息執(zhí)行的過(guò)程。到此為止,就完成了工作線程對(duì)主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行。
一. 區(qū)分進(jìn)程和線程
很多新手是區(qū)分不清線程和進(jìn)程的,沒(méi)有關(guān)系。這很正常。先看看下面這個(gè)形象的比喻:
進(jìn)程是一個(gè)工廠,工廠有它的獨(dú)立資源-工廠之間相互獨(dú)立-線程是工廠中的工人,多個(gè)工人協(xié)作完成任務(wù)-工廠內(nèi)有一個(gè)或多個(gè)工人-工人之間共享空間
如果是windows電腦中,可以打開(kāi)任務(wù)管理器,可以看到有一個(gè)后臺(tái)進(jìn)程列表。對(duì),那里就是查看進(jìn)程的地方,而且可以看到每個(gè)進(jìn)程的內(nèi)存資源信息以及cpu占有率。
所以,應(yīng)該更容易理解了:進(jìn)程是cpu資源分配的最小單位(系統(tǒng)會(huì)給它分配內(nèi)存)
最后,再用較為官方的術(shù)語(yǔ)描述一遍:
進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)
線程是cpu調(diào)度的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個(gè)進(jìn)程中可以有多個(gè)線程)
提示:
不同進(jìn)程之間也可以通信,不過(guò)代價(jià)較大
現(xiàn)在,一般通用的叫法:?jiǎn)尉€程與多線程,都是指在一個(gè)進(jìn)程內(nèi)的單和多。(所以核心還是得屬于一個(gè)進(jìn)程才行)
二. 瀏覽器是多進(jìn)程的理解了進(jìn)程與線程了區(qū)別后,接下來(lái)對(duì)瀏覽器進(jìn)行一定程度上的認(rèn)識(shí):(先看下簡(jiǎn)化理解)
瀏覽器是多進(jìn)程的
瀏覽器之所以能夠運(yùn)行,是因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源(cpu、內(nèi)存)
簡(jiǎn)單點(diǎn)理解,每打開(kāi)一個(gè)Tab頁(yè),就相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的瀏覽器進(jìn)程。
關(guān)于以上幾點(diǎn)的驗(yàn)證,請(qǐng)?jiān)俚谝粡垐D:
圖中打開(kāi)了Chrome瀏覽器的多個(gè)標(biāo)簽頁(yè),然后可以在Chrome的任務(wù)管理器中看到有多個(gè)進(jìn)程(分別是每一個(gè)Tab頁(yè)面有一個(gè)獨(dú)立的進(jìn)程,以及一個(gè)主進(jìn)程)。
感興趣的可以自行嘗試下,如果再多打開(kāi)一個(gè)Tab頁(yè),進(jìn)程正常會(huì)+1以上(不過(guò),某些版本的ie卻是單進(jìn)程的)
注意:在這里瀏覽器應(yīng)該也有自己的優(yōu)化機(jī)制,有時(shí)候打開(kāi)多個(gè)tab頁(yè)后,可以在Chrome任務(wù)管理器中看到,有些進(jìn)程被合并了(所以每一個(gè)Tab標(biāo)簽對(duì)應(yīng)一個(gè)進(jìn)程并不一定是絕對(duì)的)
三、為什么JavaScript是單線程?JavaScript語(yǔ)言的一大特點(diǎn)就是單線程,也就是說(shuō),同一個(gè)時(shí)間只能做一件事。那么,為什么JavaScript不能有多個(gè)線程呢?這樣能提高效率啊。
JavaScript的單線程,與它的用途有關(guān)。作為瀏覽器腳本語(yǔ)言,JavaScript的主要用途是與用戶(hù)互動(dòng),以及操作DOM。這決定了它只能是單線程,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?
所以,為了避免復(fù)雜性,從一誕生,JavaScript就是單線程,這已經(jīng)成了這門(mén)語(yǔ)言的核心特征,將來(lái)也不會(huì)改變。
為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)。
四. JavaScript是單線程,怎樣執(zhí)行異步的代碼?單線程就意味著,所有任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著。
js引擎執(zhí)行異步代碼而不用等待,是因有為有 消息隊(duì)列和事件循環(huán)。
消息隊(duì)列:消息隊(duì)列是一個(gè)先進(jìn)先出的隊(duì)列,它里面存放著各種消息。
事件循環(huán):事件循環(huán)是指主線程重復(fù)從消息隊(duì)列中取消息、執(zhí)行的過(guò)程。
實(shí)際上,主線程只會(huì)做一件事情,就是從消息隊(duì)列里面取消息、執(zhí)行消息,再取消息、再執(zhí)行。當(dāng)消息隊(duì)列為空時(shí),就會(huì)等待直到消息隊(duì)列變成非空。而且主線程只有在將當(dāng)前的消息執(zhí)行完成后,才會(huì)去取下一個(gè)消息。這種機(jī)制就叫做事件循環(huán)機(jī)制,取一個(gè)消息并執(zhí)行的過(guò)程叫做一次循環(huán)。
事件循環(huán)用代碼表示大概是這樣的:
while(true) { ? ?var message = queue.get(); ? ?execute(message); }
那么,消息隊(duì)列中放的消息具體是什么東西?消息的具體結(jié)構(gòu)當(dāng)然跟具體的實(shí)現(xiàn)有關(guān),但是為了簡(jiǎn)單起見(jiàn),我們可以認(rèn)為:
消息就是注冊(cè)異步任務(wù)時(shí)添加的回調(diào)函數(shù)。
再次以異步AJAX為例,假設(shè)存在如下的代碼:
$.ajax("http://segmentfault.com", function(resp) { ? ?console.log("我是響應(yīng):", resp); }); // 其他代碼 ... ... ...
主線程在發(fā)起AJAX請(qǐng)求后,會(huì)繼續(xù)執(zhí)行其他代碼。AJAX線程負(fù)責(zé)請(qǐng)求segmentfault.com,拿到響應(yīng)后,它會(huì)把響應(yīng)封裝成一個(gè)JavaScript對(duì)象,然后構(gòu)造一條消息:
// 消息隊(duì)列中的消息就長(zhǎng)這個(gè)樣子 var message = function () { ? ?callbackFn(response); }
其中的callbackFn就是前面代碼中得到成功響應(yīng)時(shí)的回調(diào)函數(shù)。
主線程在執(zhí)行完當(dāng)前循環(huán)中的所有代碼后,就會(huì)到消息隊(duì)列取出這條消息(也就是message函數(shù)),并執(zhí)行它。到此為止,就完成了工作線程對(duì)主線程的通知,回調(diào)函數(shù)也就得到了執(zhí)行。如果一開(kāi)始主線程就沒(méi)有提供回調(diào)函數(shù),AJAX線程在收到HTTP響應(yīng)后,也就沒(méi)必要通知主線程,從而也沒(méi)必要往消息隊(duì)列放消息。
用圖表示這個(gè)過(guò)程就是:
從上文中我們也可以得到這樣一個(gè)明顯的結(jié)論,就是:
異步過(guò)程的回調(diào)函數(shù),一定不在當(dāng)前這一輪事件循環(huán)中執(zhí)行。事件循環(huán)進(jìn)階:macrotask與microtask
一張圖展示JavaScript中的事件循環(huán):
一次事件循環(huán):先運(yùn)行macroTask隊(duì)列中的一個(gè),然后運(yùn)行microTask隊(duì)列中的所有任務(wù)。接著開(kāi)始下一次循環(huán)(只是針對(duì)macroTask和microTask,一次完整的事件循環(huán)會(huì)比這個(gè)復(fù)雜的多)。
JS中分為兩種任務(wù)類(lèi)型:macrotask和microtask,在ECMAScript中,microtask稱(chēng)為jobs,macrotask可稱(chēng)為task
它們的定義?區(qū)別?簡(jiǎn)單點(diǎn)可以按如下理解:
macrotask(又稱(chēng)之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
每一個(gè)task會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它
瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)task執(zhí)行結(jié)束后,在下一個(gè) task 執(zhí)行開(kāi)始前,對(duì)頁(yè)面進(jìn)行重新渲染
(task->渲染->task->...)
microtask(又稱(chēng)為微任務(wù)),可以理解是在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說(shuō),在當(dāng)前task任務(wù)后,下一個(gè)task之前,在渲染之前
所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會(huì)更快,因?yàn)闊o(wú)需等渲染
也就是說(shuō),在某一個(gè)macrotask執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)
分別很么樣的場(chǎng)景會(huì)形成macrotask和microtask呢?
macroTask: 主代碼塊, setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)macrotask)
microTask: process.nextTick, Promise, Object.observe, MutationObserver
補(bǔ)充:在node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于Promise,也就是可以簡(jiǎn)單理解為:在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分,然后才會(huì)執(zhí)行微任務(wù)中的Promise部分。
另外,setImmediate則是規(guī)定:在下一次Event Loop(宏任務(wù))時(shí)觸發(fā)(所以它是屬于優(yōu)先級(jí)較高的宏任務(wù)),(Node.js文檔中稱(chēng),setImmediate指定的回調(diào)函數(shù),總是排在setTimeout前面),所以setImmediate如果嵌套的話,是需要經(jīng)過(guò)多個(gè)Loop才能完成的,而不會(huì)像process.nextTick一樣沒(méi)完沒(méi)了。
實(shí)踐:上代碼
我們以setTimeout、process.nextTick、promise為例直觀感受下兩種任務(wù)隊(duì)列的運(yùn)行方式。
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");
別著急看答案,先以上面的理論自己想想,運(yùn)行結(jié)果會(huì)是啥?
最終結(jié)果是這樣的:
main1 promise main2 process.nextTick1 promise then setTimeout process.nextTick2
process.nextTick 和 promise then在 setTimeout 前面輸出,已經(jīng)證明了macroTask和microTask的執(zhí)行順序。但是有一點(diǎn)必須要指出的是。上面的圖容易給人一個(gè)錯(cuò)覺(jué),就是主進(jìn)程的代碼執(zhí)行之后,會(huì)先調(diào)用macroTask,再調(diào)用microTask,這樣在第一個(gè)循環(huán)里一定是macroTask在前,microTask在后。
但是最終的實(shí)踐證明:在第一個(gè)循環(huán)里,process.nextTick1和promise then這兩個(gè)microTask是在setTimeout這個(gè)macroTask里之前輸出的,這是為什么呢?
因?yàn)橹鬟M(jìn)程的代碼也屬于macroTask(這一點(diǎn)我比較疑惑的是主進(jìn)程都是一些同步代碼,而macroTask和microTask包含的都是一些異步任務(wù),為啥主進(jìn)程的代碼會(huì)被劃分為macroTask,不過(guò)從實(shí)踐來(lái)看確實(shí)是這樣,而且也有理論支撐:【翻譯】Promises/A+規(guī)范)。
主進(jìn)程這個(gè)macroTask(也就是main1、promise和main2)執(zhí)行完了,自然會(huì)去執(zhí)行process.nextTick1和promise then這兩個(gè)microTask。這是第一個(gè)循環(huán)。之后的setTimeout和process.nextTick2屬于第二個(gè)循環(huán)
別看上面那段代碼好像特別繞,把原理弄清楚了,都一樣 ~
requestAnimationFrame、Object.observe(已廢棄) 和 MutationObserver這三個(gè)任務(wù)的運(yùn)行機(jī)制大家可以從上面看到,不同的只是具體用法不同。重點(diǎn)說(shuō)下UI rendering。在HTML規(guī)范:event-loop-processing-model里敘述了一次事件循環(huán)的處理過(guò)程,在處理了macroTask和microTask之后,會(huì)進(jìn)行一次Update the rendering,其中細(xì)節(jié)比較多,總的來(lái)說(shuō)會(huì)進(jìn)行一次UI的重新渲染。
這里就直接引用一張圖片來(lái)協(xié)助理解:(參考自Philip Roberts的演講《Help, I’m stuck in an event-loop》)
上圖大致描述就是:
主線程運(yùn)行時(shí)會(huì)產(chǎn)生執(zhí)行棧,棧中的代碼調(diào)用某些api時(shí),它們會(huì)在事件隊(duì)列中添加各種事件(當(dāng)滿(mǎn)足觸發(fā)條件后,如ajax請(qǐng)求完畢)
而棧中的代碼執(zhí)行完畢,就會(huì)讀取事件隊(duì)列中的事件,去執(zhí)行那些回調(diào)
如此循環(huán)
注意,總是要等待棧中的代碼執(zhí)行完畢后才會(huì)去讀取事件隊(duì)列中的事件
五. 最后看到這里,應(yīng)該對(duì)JS的運(yùn)行機(jī)制有一定的理解了吧。
參考:
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_...
我不是大神,也不是什么牛人,寫(xiě)這個(gè)號(hào)的目的是為了記錄我自學(xué) web全棧 的筆記。
對(duì) 全棧修煉 有興趣的朋友可以掃下方二維碼關(guān)注我的公眾號(hào)
我會(huì)不定期更新有價(jià)值的內(nèi)容,長(zhǎng)期運(yùn)營(yíng)。
關(guān)注公眾號(hào)并回復(fù) 福利 可領(lǐng)取免費(fèi)學(xué)習(xí)資料,福利詳情請(qǐng)猛戳: Python、Java、Linux、Go、node、vue、react、javaScript
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/96378.html
摘要:深入理解引擎的執(zhí)行機(jī)制靈魂三問(wèn)為什么是單線程的為什么需要異步單線程又是如何實(shí)現(xiàn)異步的呢中的中的說(shuō)說(shuō)首先請(qǐng)牢記點(diǎn)是單線程語(yǔ)言的是的執(zhí)行機(jī)制。 深入理解JS引擎的執(zhí)行機(jī)制 1.靈魂三問(wèn) : JS為什么是單線程的? 為什么需要異步? 單線程又是如何實(shí)現(xiàn)異步的呢? 2.JS中的event loop(1) 3.JS中的event loop(2) 4.說(shuō)說(shuō)setTimeout 首先,請(qǐng)牢記2...
摘要:解析首先簡(jiǎn)稱(chēng)是由歐洲計(jì)算機(jī)制造商協(xié)會(huì)制定的標(biāo)準(zhǔn)化腳本程序設(shè)計(jì)語(yǔ)言。級(jí)在年月份成為的提議,由核心與兩個(gè)模塊組成。通過(guò)引入統(tǒng)一方式載入和保存文檔和文檔驗(yàn)證方法對(duì)進(jìn)行進(jìn)一步擴(kuò)展。其中表示的標(biāo)記位正好是低三位都是。但提案被拒絕了。 JS高級(jí)入門(mén)教程 目錄 本文章定位及介紹 JavaScript與ECMAScript的關(guān)系 DOM的本質(zhì)及DOM級(jí)介紹 JS代碼特性 基本類(lèi)型與引用類(lèi)型 JS的垃...
摘要:事件循環(huán)當(dāng)主線程中的任務(wù)執(zhí)行完畢后,會(huì)從任務(wù)隊(duì)列中獲取任務(wù)一個(gè)個(gè)的放在棧中執(zhí)行去執(zhí)行,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱(chēng)為事件循環(huán)。 寫(xiě)在前面 說(shuō)起javascript(以下簡(jiǎn)稱(chēng)js)這門(mén)語(yǔ)言,相信大家已經(jīng)非常熟悉了,不管是前端開(kāi)發(fā)還是后端開(kāi)發(fā)幾乎無(wú)時(shí)無(wú)刻都要跟它打交道。雖說(shuō)開(kāi)發(fā)者每天幾乎都要操作js,但是你真的確定你掌握了js的運(yùn)行機(jī)制嗎!下面我們就來(lái)聊聊這話題。 Ja...
摘要:調(diào)用棧是單線程編程語(yǔ)言,意味著它只有單一的調(diào)用棧。調(diào)用棧是一種數(shù)據(jù)結(jié)構(gòu),基本記錄了程序運(yùn)行的位置。舉個(gè)例子,先來(lái)看如下所示的代碼當(dāng)引擎開(kāi)始執(zhí)行這段代碼時(shí),調(diào)用棧將是空的。這正是拋出異常時(shí)棧追蹤的構(gòu)造過(guò)程這基本上就是異常拋出時(shí)調(diào)用棧的狀態(tài)。 原文 How JavaScript works: an overview of the engine, the runtime, and the c...
摘要:對(duì)于通常的特別是那些具備并行計(jì)算多線程背景知識(shí)的來(lái)講,的異步處理著實(shí)稱(chēng)得上詭異。而這個(gè)詭異從結(jié)果上講,是由的單線程這個(gè)特性所導(dǎo)致的。的特性之一是單線程,也即是從頭到尾,都在同一根線程下運(yùn)行。而這兩者的不同,便在于單線程和多線程上。 對(duì)于通常的developer(特別是那些具備并行計(jì)算/多線程背景知識(shí)的developer)來(lái)講,js的異步處理著實(shí)稱(chēng)得上詭異。而這個(gè)詭異從結(jié)果上講,是由js...
閱讀 3734·2021-11-22 13:52
閱讀 3614·2019-12-27 12:20
閱讀 2392·2019-08-30 15:55
閱讀 2148·2019-08-30 15:44
閱讀 2265·2019-08-30 13:16
閱讀 579·2019-08-28 18:19
閱讀 1890·2019-08-26 11:58
閱讀 3443·2019-08-26 11:47