摘要:事件觸發(fā)線程主要負(fù)責(zé)將準(zhǔn)備好的事件交給引擎線程執(zhí)行。進(jìn)程瀏覽器渲染進(jìn)程瀏覽器內(nèi)核,主要負(fù)責(zé)頁(yè)面的渲染執(zhí)行以及事件的循環(huán)。第二輪循環(huán)結(jié)束。
將自己讀到的比較好的文章分享出來(lái),大家互相學(xué)習(xí),各位大佬有好的文章也可以留個(gè)鏈接互相學(xué)習(xí),萬(wàn)分感謝!線程與進(jìn)程
關(guān)于線程與進(jìn)程的關(guān)系可以用下面的圖進(jìn)行說(shuō)明:
進(jìn)程好比圖中的工廠,有多帶帶的專屬自己的工廠資源。
線程好比圖中的工人,多個(gè)工人在一個(gè)工廠中協(xié)作工作,工廠與工人是 1:n的關(guān)系。
多個(gè)工廠之間獨(dú)立存在。
而官方的說(shuō)法是:
進(jìn)程是 CPU資源分配的最小單位。
線程是 CPU調(diào)度的最小單位。
從更直觀的例子來(lái)看,可以打開任務(wù)管理器查看,第一個(gè) tab便是進(jìn)程列表,每一個(gè)進(jìn)程占有的 CPU資源和內(nèi)存資源的比例很直觀的展示出來(lái)。
為什么js是單線程初學(xué)計(jì)算機(jī)語(yǔ)言的時(shí)候,無(wú)論是 C、C++還是 JAVA,都是支持多線程,偏偏 JavaScript是單線程,不支持多線程,這也跟 JavaScript的作用有關(guān),都知道 JavaScript是主要運(yùn)行在瀏覽器的腳本語(yǔ)言,最終操作的是頁(yè)面的 DOM結(jié)構(gòu),當(dāng)兩個(gè) JavaScript腳本同時(shí)修改頁(yè)面的同一個(gè) DOM節(jié)點(diǎn)時(shí),瀏覽器該執(zhí)行哪個(gè)呢?所以當(dāng)時(shí)設(shè)計(jì) JavaScript時(shí),便要求當(dāng)前修改操作完成后方可進(jìn)行下一步修改操作。
瀏覽器是支持多進(jìn)程同樣我們打開瀏覽器的任務(wù)管理器,以下圖為例:
瀏覽器的每一個(gè) tab頁(yè)都是一個(gè)進(jìn)程,有對(duì)應(yīng)的內(nèi)存占用空間、 CPU使用量以及進(jìn)程ID。 新打開一個(gè) tab頁(yè)時(shí),都會(huì)新建一個(gè)進(jìn)程,所以就有一個(gè) tab頁(yè)對(duì)應(yīng)一個(gè)進(jìn)程的說(shuō)法,但是這種說(shuō)法又是錯(cuò)誤的,因?yàn)闉g覽器有自己的優(yōu)化機(jī)制,當(dāng)我們打開多個(gè)空白的 tab頁(yè)時(shí),瀏覽器會(huì)將這多個(gè)空白頁(yè)的進(jìn)程合并為一個(gè),從而減少了進(jìn)程的數(shù)量個(gè)數(shù)。
瀏覽器內(nèi)核瀏覽器內(nèi)核中有多個(gè)進(jìn)程在同步工作,今天涉及到的瀏覽器的進(jìn)程主要包括以下進(jìn)程:
Browser 進(jìn)程
主進(jìn)程,主要負(fù)責(zé)頁(yè)面管理以及管理其他進(jìn)程的創(chuàng)建和銷毀等,常駐的線程有:
GUI渲染線程
JS引擎線程
事件觸發(fā)線程
定時(shí)器觸發(fā)線程
HTTP請(qǐng)求線程
GUI渲染線程
主要負(fù)責(zé)頁(yè)面的渲染,解析HTML、CSS,構(gòu)建DOM樹,布局和繪制等。
當(dāng)界面需要重繪或者由于某種操作引發(fā)回流時(shí),將執(zhí)行該線程。
該線程與JS引擎線程互斥,當(dāng)執(zhí)行JS引擎線程時(shí),GUI渲染會(huì)被掛起,當(dāng)任務(wù)隊(duì)列空閑時(shí),JS引擎才會(huì)去執(zhí)行GUI渲染。
JS引擎線程
該線程當(dāng)然是主要負(fù)責(zé)處理 JavaScript腳本,執(zhí)行代碼。
也是主要負(fù)責(zé)執(zhí)行準(zhǔn)備好待執(zhí)行的事件,即定時(shí)器計(jì)數(shù)結(jié)束,或者異步請(qǐng)求成功并正確返回時(shí),將依次進(jìn)入任務(wù)隊(duì)列,等待 JS引擎線程的執(zhí)行。
當(dāng)然,該線程與 GUI渲染線程互斥,當(dāng) JS引擎線程執(zhí)行 JavaScript腳本時(shí)間過(guò)長(zhǎng),將導(dǎo)致頁(yè)面渲染的阻塞。
事件觸發(fā)線程
主要負(fù)責(zé)將準(zhǔn)備好的事件交給 JS引擎線程執(zhí)行。
比如 setTimeout定時(shí)器計(jì)數(shù)結(jié)束, ajax等異步請(qǐng)求成功并觸發(fā)回調(diào)函數(shù),或者用戶觸發(fā)點(diǎn)擊事件時(shí),該線程會(huì)將整裝待發(fā)的事件依次加入到任務(wù)隊(duì)列的隊(duì)尾,等待 JS引擎線程的執(zhí)行。
定時(shí)器觸發(fā)線程
顧名思義,負(fù)責(zé)執(zhí)行異步定時(shí)器一類的函數(shù)的線程,如: setTimeout,setInterval。
主線程依次執(zhí)行代碼時(shí),遇到定時(shí)器,會(huì)將定時(shí)器交給該線程處理,當(dāng)計(jì)數(shù)完畢后,事件觸發(fā)線程會(huì)將計(jì)數(shù)完畢后的事件加入到任務(wù)隊(duì)列的尾部,等待JS引擎線程執(zhí)行。
HTTP請(qǐng)求線程
顧名思義,負(fù)責(zé)執(zhí)行異步請(qǐng)求一類的函數(shù)的線程,如: Promise,anxios,ajax等。
主線程依次執(zhí)行代碼時(shí),遇到異步請(qǐng)求,會(huì)將函數(shù)交給該線程處理,當(dāng)監(jiān)聽到狀態(tài)碼變更,如果有回調(diào)函數(shù),事件觸發(fā)線程會(huì)將回調(diào)函數(shù)加入到任務(wù)隊(duì)列的尾部,等待JS引擎線程執(zhí)行。
多個(gè)線程之間配合工作,各司其職。
Render 進(jìn)程
瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核),主要負(fù)責(zé)頁(yè)面的渲染、JS執(zhí)行以及事件的循環(huán)。同步任務(wù)和異步任務(wù)
同步任務(wù) 即可以立即執(zhí)行的任務(wù),例如 console.log() 打印一條日志、聲明一個(gè)變量或者執(zhí)行一次加法操作等。
異步任務(wù) 相反不會(huì)立即執(zhí)行的事件任務(wù)。異步任務(wù)包括宏任務(wù)和微任務(wù)(后面會(huì)進(jìn)行解釋~)。
常見的異步操作:
Ajax
DOM的事件操作
setTimeout
Promise的then方法
Node的讀取文件
下圖給出了同步任務(wù)與異步任務(wù)的執(zhí)行流程:
棧 就像是一個(gè)容器,任務(wù)都是在棧中執(zhí)行。
主線程 就像是操作員,負(fù)責(zé)執(zhí)行棧中的任務(wù)。
任務(wù)隊(duì)列 就像是等待被加工的物品。
異步任務(wù)完成注冊(cè)后會(huì)將回調(diào)函數(shù)加入任務(wù)隊(duì)列等待主線程執(zhí)行。
執(zhí)行棧中的同步任務(wù)執(zhí)行完畢后,會(huì)查看并讀取任務(wù)隊(duì)列中的事件函數(shù),于是任務(wù)隊(duì)列的函數(shù)結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
那么任務(wù)到底是如何入棧和出棧的呢?可以用一小段代碼進(jìn)行解釋。
入棧與出棧以下面的代碼為例:
console.log(1); function fn1(){ console.log(2); } function fn2(){ console.log(3); fn1(); } setTimeout(function(){ console.log(4); }, 2000); fn2(); console.log(5);
所以上面代碼運(yùn)行的結(jié)果為:1,3,2,5,4。
宏任務(wù)和微任務(wù)異步任務(wù)分為宏任務(wù)和微任務(wù),宏任務(wù)隊(duì)列可以有多個(gè),微任務(wù)隊(duì)列只有一個(gè)。
宏任務(wù)和微任務(wù)的執(zhí)行方式在瀏覽器和 Node 中有差異。
宏任務(wù)(macrotask)script(全局任務(wù)), setTimeout, setInterval, setImmediate, I/O, UI rendering微任務(wù)(macrotask)
process.nextTick, Promise.then(), Object.observe, MutationObserver
在微任務(wù)中 process.nextTick 優(yōu)先級(jí)高于Promise
當(dāng)一個(gè)異步任務(wù)入棧時(shí),主線程判斷該任務(wù)為異步任務(wù),并把該任務(wù)交給異步處理模塊處理,當(dāng)異步處理模塊處理完打到觸發(fā)條件時(shí),根據(jù)任務(wù)的類型,將回調(diào)函數(shù)壓入任務(wù)隊(duì)列。
如果是宏任務(wù),則新增一個(gè)宏任務(wù)隊(duì)列,任務(wù)隊(duì)列中的宏任務(wù)可以有多個(gè)來(lái)源。
如果是微任務(wù),則直接壓入微任務(wù)隊(duì)列。
所以上圖的任務(wù)隊(duì)列可以繼續(xù)細(xì)化一下:
那么當(dāng)棧為空時(shí),宏任務(wù)和微任務(wù)的執(zhí)行機(jī)制又是什么呢?
Event Loop到這里,除了上面的問(wèn)題,我們已經(jīng)把事件循環(huán)的最基本的處理方式搞清楚了,但具體到異步任務(wù)中的宏任務(wù)和微任務(wù),還沒(méi)有弄明白。我們可以先順一遍執(zhí)行機(jī)制:
從全局任務(wù) script開始,任務(wù)依次進(jìn)入棧中,被主線程執(zhí)行,執(zhí)行完后出棧。
遇到異步任務(wù),交給異步處理模塊處理,對(duì)應(yīng)的異步處理線程處理異步任務(wù)需要的操作,例如定時(shí)器的計(jì)數(shù)和異步請(qǐng)求監(jiān)聽狀態(tài)的變更。
當(dāng)異步任務(wù)達(dá)到可執(zhí)行狀態(tài)時(shí),事件觸發(fā)線程將回調(diào)函數(shù)加入任務(wù)隊(duì)列,等待棧為空時(shí),依次進(jìn)入棧中執(zhí)行。
到這問(wèn)題就來(lái)了,當(dāng)異步任務(wù)進(jìn)入棧執(zhí)行時(shí),是宏任務(wù)還是微任務(wù)呢?
由于執(zhí)行代碼入口都是全局任務(wù) script,而全局任務(wù)屬于宏任務(wù),所以當(dāng)棧為空,同步任務(wù)任務(wù)執(zhí)行完畢時(shí),會(huì)先執(zhí)行微任務(wù)隊(duì)列里的任務(wù)。
微任務(wù)隊(duì)列里的任務(wù)全部執(zhí)行完畢后,會(huì)讀取宏任務(wù)隊(duì)列中拍最前的任務(wù)。
執(zhí)行宏任務(wù)的過(guò)程中,遇到微任務(wù),依次加入微任務(wù)隊(duì)列。
??蘸?,再次讀取微任務(wù)隊(duì)列里的任務(wù),依次類推。
實(shí)例解析回到最開始的那段代碼,現(xiàn)在我們可以一步一步的看一下執(zhí)行順序。
console.log(1); setTimeout(function(){ console.log(2); }, 0); setTimeout(function(){ console.log(3) },2000) console.log(4);
從全局任務(wù)入口,首先打印日志 1,
遇到宏任務(wù) setTimeout,交給異步處理模塊,我們暫且先記為 setTimeout1,
再次遇到宏任務(wù) setTimeout,交給異步處理模塊,我們暫且先記為 setTimeout2,
順序執(zhí)行,打印日志 4,
此時(shí)同步任務(wù)已執(zhí)行完畢,讀取宏任務(wù)隊(duì)列的任務(wù),先執(zhí)行 setTimeout1的回調(diào)函數(shù),因?yàn)槎〞r(shí)器的等待時(shí)間為 0秒,所以會(huì)直接輸出 2,但是 W3C在 HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求 setTimeout中低于 4ms的時(shí)間間隔算為 4ms,
由于瀏覽器在執(zhí)行以上三步時(shí),并未耗時(shí)很久,所以當(dāng)宏任務(wù) setTimeout1執(zhí)行完時(shí), setTimeout2的等待時(shí)間并未結(jié)束,所以在 2秒后打印日志 3,實(shí)際上并未等待2秒。
下面我們可以再看一個(gè)實(shí)例:
setTimeout(function(){ console.log(1); Promise.resolve().then(function(){ console.log(2) }) },0) setTimeout(function(){ console.log(3) },0) Promise.resolve().then(function(){ console.log(4) }); console.log(5)
當(dāng)代碼中遇到了異步請(qǐng)求的事件,又該如何執(zhí)行,根據(jù)上面總結(jié)的執(zhí)行機(jī)制,又該得到什么樣的結(jié)果?
第一輪循環(huán)
同樣從全局任務(wù)入口,遇到宏任務(wù) setTimeout,交給異步處理模塊,我們暫且先記為 setTimeout1,由于等待時(shí)間為 0,直接加入宏任務(wù)隊(duì)列。
再次遇到宏任務(wù) setTimeout,交給異步處理模塊,我們暫且先記為 setTimeout2,同樣直接加入宏任務(wù)隊(duì)列。
遇到微任務(wù) then(),加入微任務(wù)隊(duì)列。
最后遇到打印語(yǔ)句,直接打印日志 5。
第一輪循環(huán)結(jié)束后,可以畫出下圖:
第二輪循環(huán)
??蘸螅葓?zhí)行微任務(wù)隊(duì)列中的 then()方法,輸出 4,此時(shí)微任務(wù)隊(duì)列為空。
讀取宏任務(wù)隊(duì)列的最靠前的任務(wù) setTimeout1。
先直接執(zhí)行打印語(yǔ)句,打印日志 1,又遇到微任務(wù) then(),加入微任務(wù)隊(duì)列。第二輪循環(huán)結(jié)束。
第三輪循環(huán)
先執(zhí)行微任務(wù)隊(duì)列中的 then()方法,輸出 2,此時(shí)微任務(wù)隊(duì)列為空。
繼續(xù)讀取宏任務(wù)隊(duì)列的最靠前的任務(wù) setTimeout2。
直接執(zhí)行打印語(yǔ)句,打印日志 3。第三輪循環(huán)結(jié)束,執(zhí)行完畢。
最后我們是我們的boss,歡迎大家在評(píng)論區(qū)留言寫出自己心中的那個(gè)正確答案。
console.log(1); setTimeout(function(){ console.log(2); new Promise(function(resolve, reject){ console.log(3); resolve(); }).then(function(){ console.log(4); }) }) new Promise(function(resolve, reject){ console.log(5); resolve(); }).then(function(){ console.log(6); }) setTimeout(function(){ console.log(7) }) setTimeout(function(){ console.log(8); new Promise(function(resolve, reject){ console.log(9); resolve(); }).then(function(){ console.log(10); }) }) new Promise(function(resolve){ console.log(11); resolve(); }).then(function(){ console.log(12) }) console.log(13)
github地址:https://github.com/ABCDdouyaer/a_article_per_day/tree/master/0001
原文鏈接:https://mp.weixin.qq.com/s/9_hZX_xWSr3Gd1X_2_WOsA
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/114271.html
摘要:主線程不斷重復(fù)上面的三步,此過(guò)程也就是常說(shuō)的事件循環(huán)。所以主線程代碼執(zhí)行時(shí)間過(guò)長(zhǎng),會(huì)阻塞事件循環(huán)的執(zhí)行。參考資料這一次,徹底弄懂執(zhí)行機(jī)制任務(wù)隊(duì)列的順序機(jī)制事件循環(huán)搞懂異步事件輪詢與中的事件循環(huán) 1. 說(shuō)明 讀過(guò)本文章后,您能知道: JavaScript代碼在瀏覽器中的執(zhí)行機(jī)制和事件循環(huán) 面試中經(jīng)常遇到的代碼輸出順序問(wèn)題 首先通過(guò)一段代碼來(lái)驗(yàn)證你是否了解代碼輸出順序,如果你不知道輸出...
摘要:了解事件循環(huán)機(jī)制有助于理解的執(zhí)行過(guò)程,同時(shí)這也是面試常見題。那么這個(gè)回調(diào)函數(shù)將在何時(shí)由誰(shuí)執(zhí)行呢已知是瀏覽器環(huán)境提供的,因此瀏覽器將對(duì)它進(jìn)行處理,瀏覽器會(huì)在本次事件完成,即計(jì)時(shí)結(jié)束后,將回調(diào)函數(shù)加入循環(huán)隊(duì)列中,然后等待被加入執(zhí)行棧執(zhí)行。 如果有人問(wèn)JavaScript是什么,也許你會(huì)說(shuō)它是一個(gè)單線程、非阻塞、異步、解釋型的腳本語(yǔ)言。那么作為一個(gè)單線程語(yǔ)言,它是怎么實(shí)現(xiàn)非阻塞、異步的?這就...
摘要:如果沒(méi)有其他異步任務(wù)要處理比如到期的定時(shí)器,會(huì)一直停留在這個(gè)階段,等待請(qǐng)求返回結(jié)果。執(zhí)行的執(zhí)行事件關(guān)閉請(qǐng)求的,例如事件循環(huán)的每一次循環(huán)都需要依次經(jīng)過(guò)上述的階段。因此,才會(huì)早于執(zhí)行。 showImg(https://segmentfault.com/img/bVbnY76); 概念 同步任務(wù)(Synchronous) 在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù) ...
摘要:主線程要明確的一點(diǎn)是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個(gè)事件。主線程循環(huán)即主線程會(huì)不停的從執(zhí)行棧中讀取事件,會(huì)執(zhí)行完所有棧中的同步代碼。以上參考資料詳解中的事件循環(huán)機(jī)制中的事件循環(huán)運(yùn)行機(jī)制詳解再談 showImg(https://segmentfault.com/img/remote/1460000015317437?w=1920&h=1080); 前言 大家都...
摘要:前端基礎(chǔ)進(jìn)階正是圍繞這條線索慢慢展開,而事件循環(huán)機(jī)制,則是這條線索的最關(guān)鍵的知識(shí)點(diǎn)。特別是中正式加入了對(duì)象之后,對(duì)于新標(biāo)準(zhǔn)中事件循環(huán)機(jī)制的理解就變得更加重要。之后全局上下文進(jìn)入函數(shù)調(diào)用棧。 showImg(https://segmentfault.com/img/remote/1460000008811705); JavaScript的學(xué)習(xí)零散而龐雜,因此很多時(shí)候我們學(xué)到了一些東西,但...
摘要:事件循環(huán)機(jī)制首先區(qū)分進(jìn)程和線程進(jìn)程是資源分配的最小單位系統(tǒng)會(huì)給它分配內(nèi)存不同的進(jìn)程之間是可以同學(xué)的,如管道命名管道消息隊(duì)列一個(gè)進(jìn)程里有單個(gè)或多個(gè)線程瀏覽器是多進(jìn)程的,因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源內(nèi)存打開會(huì)有一個(gè)主進(jìn)程,每打開一個(gè)頁(yè)就有一個(gè)獨(dú) JS JavaScript事件循環(huán)機(jī)制 首先區(qū)分進(jìn)程和線程 進(jìn)程是cpu資源分配的最小單位(系統(tǒng)會(huì)給它分配內(nèi)存) 不同的進(jìn)程之間是可以同學(xué)的,如...
閱讀 2099·2021-11-24 09:39
閱讀 1568·2021-10-11 10:59
閱讀 2513·2021-09-24 10:28
閱讀 3387·2021-09-08 09:45
閱讀 1279·2021-09-07 10:06
閱讀 1674·2019-08-30 15:53
閱讀 2070·2019-08-30 15:53
閱讀 1427·2019-08-30 15:53