重點來了,我們可以看到褐桌,上面提到了這么多的進程,那么象迎,對于普通的前端操作來說荧嵌,最終要的是什么呢?答案是渲染進程砾淌。因為頁面的渲染啦撮,JS的執(zhí)行,事件的觸發(fā)汪厨,都在這個進程內(nèi)進行的赃春。接下來重點分析這個進程。
之前講到過劫乱,進程一般是多線程的织中,忘了可以再復習下前面第一節(jié),進程和線程衷戈,那么瀏覽器的渲染進程又包括哪些線程狭吼。
1.渲染進程包括哪些線程
- GUI渲染線程
- 負責渲染瀏覽器界面,解析HTML殖妇,CSS刁笙,構(gòu)建DOM樹和RenderObject樹,布局和繪制等。
- 當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時疲吸,該線程就會執(zhí)行
- 注意座每,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結(jié)了)摘悴,GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行峭梳。
- JS引擎線程(單線程)
- 也稱為JS內(nèi)核,負責處理Javascript腳本程序烦租。(例如常常聽到的谷歌瀏覽器的V8引擎延赌,新版火狐的JaegerMonkey引擎等)
- JS引擎線程負責解析Javascript腳本,運行代碼叉橱。
- JS引擎一直等待著任務隊列中任務的到來挫以,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
- 同樣注意窃祝,GUI渲染線程與JS引擎線程是互斥的掐松,所以如果JS執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫粪小,導致頁面渲染加載阻塞大磺。
- 事件觸發(fā)線程
- 歸屬于渲染進程而不是JS引擎,用來控制事件輪詢(可以理解探膊,JS引擎自己都忙不過來杠愧,需要瀏覽器另開線程協(xié)助)
- 當JS引擎執(zhí)行代碼塊如鼠標點擊、AJAX異步請求等逞壁,會將對應任務添加到事件觸發(fā)線程中
- 當對應的事件符合觸發(fā)條件被觸發(fā)時流济,該線程會把事件添加到待處理任務隊列的隊尾,等待JS引擎的處理
- 注意腌闯,由于JS的單線程關(guān)系绳瘟,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行)
-
定時觸發(fā)器線程
- 定時器setInterval與setTimeout所在線程
- 瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因為JavaScript引擎是單線程的, 如果任務隊列處于阻塞線程狀態(tài)就會影響記計時的準確)
- 因此通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件隊列中姿骏,等待JS引擎空閑后執(zhí)行)
- 注意糖声,W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms分瘦。
-
異步http請求線程
- 用于處理請求XMLHttpRequest蘸泻,在連接后是通過瀏覽器新開一個線程請求。如ajax擅腰,是瀏覽器新開一個http線程
-
將檢測到狀態(tài)變更(如ajax返回結(jié)果)時蟋恬,如果設置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件趁冈,將這個回調(diào)再放入js引擎線程的事件隊列中歼争。再由JavaScript引擎執(zhí)行拜马。
2084336019-5a65972413011.png
知道了這幾個線程,那么通過這幾個線程沐绒,js是怎么執(zhí)行的呢俩莽?
2.渲染進程中的線程之間的關(guān)系
GUI渲染線程與JS引擎線程互斥
由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和GUI線程同時運行)乔遮,那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了扮超。
因此為了防止渲染出現(xiàn)不可預期的結(jié)果,瀏覽器設置GUI渲染線程與JS引擎為互斥的關(guān)系蹋肮,當JS引擎執(zhí)行時GUI線程會被掛起出刷,
GUI更新則會被保存在一個隊列中等到JS引擎線程空閑時立即被執(zhí)行。
JS阻塞頁面加載
從上述的互斥關(guān)系坯辩,可以推導出馁龟,JS如果執(zhí)行時間過長就會阻塞頁面。
譬如漆魔,假設JS引擎正在進行巨量的計算坷檩,所以JS引擎很可能很久很久后才能空閑,所以導致頁面渲染加載阻塞改抡。這就牽扯到script標簽在html中的存放位置矢炼。具體可以看我另一篇文章 為什么script標簽一般放在body下面
3.js引擎是單線程的
我們知道js是單線程的。也就是說阿纤,同一個時間只能做一件事句灌。那么,為什么JavaScript不能有多個線程呢欠拾?這樣能提高效率啊涯塔。
參考阮一峰大神的文章js事件輪詢(Event Loop)
- JavaScript的單線程,與它的用途有關(guān)清蚀。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動爹谭,以及操作DOM枷邪。這決定了它只能是單線程,否則會帶來很復雜的同步問題诺凡。比如东揣,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內(nèi)容腹泌,另一個線程刪除了這個節(jié)點嘶卧,這時瀏覽器應該以哪個線程為準?
- 所以凉袱,為了避免復雜性芥吟,從一誕生侦铜,JavaScript就是單線程,這已經(jīng)成了這門語言的核心特征钟鸵,將來也不會改變钉稍。
- 為了利用多核CPU的計算能力,HTML5提出Web Worker標準棺耍,允許JavaScript腳本創(chuàng)建多個線程贡未,但是子線程完全受主線程控制,且不得操作DOM蒙袍。所以俊卤,這個新標準并沒有改變JavaScript單線程的本質(zhì)。
4.js事件輪詢
上面我們已經(jīng)知道JS引擎是單線程害幅,任務應該是按順序執(zhí)行的消恍,那么怎么會有同步異步之說?
- 單線程就意味著矫限,所有任務需要排隊哺哼,前一個任務結(jié)束,才會執(zhí)行后一個任務叼风。如果前一個任務耗時很長取董,后一個任務就不得不一直等著。
- 如果排隊是因為計算量大无宿,CPU忙不過來茵汰,倒也算了,但是很多時候CPU是閑著的孽鸡,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網(wǎng)絡讀取數(shù)據(jù))蹂午,不得不等著結(jié)果出來,再往下執(zhí)行彬碱。
- JavaScript語言的設計者意識到豆胸,這時主線程完全可以不管IO設備,掛起處于等待中的任務巷疼,先運行排在后面的任務晚胡。等到IO設備返回了結(jié)果,再回過頭嚼沿,把掛起的任務繼續(xù)執(zhí)行下去估盘。
- 于是,所有任務可以分成兩種骡尽,一種是同步任務(synchronous)遣妥,另一種是異步任務(asynchronous)。同步任務指的是攀细,在主線程上排隊執(zhí)行的任務箫踩,只有前一個任務執(zhí)行完畢爱态,才能執(zhí)行后一個任務;異步任務指的是班套,不進入主線程肢藐、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程吱韭,某個異步任務可以執(zhí)行了吆豹,該任務才會進入主線程執(zhí)行。
理解了同步異步理盆。其實其最本質(zhì)原因就是基于js的事件輪詢機制痘煤。
- 所有同步任務都在主線程(即js引擎線程)上執(zhí)行,形成一個執(zhí)行棧
- 而異步任務均由事件觸發(fā)線程控制猿规,其有一個任務隊列衷快。只要異步任務有了運行結(jié)果,就在"任務隊列"之中放置回調(diào)事件姨俩。異步任務必須指定回調(diào)函數(shù)蘸拔,當主線程開始執(zhí)行異步任務,就是執(zhí)行對應的回調(diào)函數(shù)环葵。所以所謂"回調(diào)函數(shù)"(callback)调窍,就是那些會被主線程掛起來的代碼。
- 一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢张遭,系統(tǒng)就會讀取"任務隊列"邓萨,按順序結(jié)束等待狀態(tài),進入執(zhí)行棧菊卷,開始執(zhí)行缔恳。
- 主線程不斷重復上面的第三步
- 只要主線程空了,就會去讀取"任務隊列"洁闰,這個過程會不斷重復歉甚。這就是JavaScript的運行機制。又稱為Event Loop(事件循環(huán)或者輪詢)扑眉。
5.定時器觸發(fā)線程
上述事件循環(huán)機制的核心是:JS引擎線程和事件觸發(fā)線程
js來控制主線程铃芦,事件觸發(fā)來控制任務隊列就如主線程。
為什么要單獨的定時器線程襟雷?因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確,因此很有必要單獨開一個線程用來計時仁烹。
什么時候會用到定時器線程酵使?當使用setTimeout或setInterval時钞速,它需要定時器線程計時,計時完成后就會將特定的事件推入事件觸發(fā)線程的任務隊列中逢净。等待進入主線程執(zhí)行。
譬如:
setTimeout(function(){
console.log('hello!');
}, 1000);
這段代碼的作用是當1000毫秒計時完畢后(由定時器線程計時)育拨,將回調(diào)函數(shù)推入事件隊列中,等待主線程執(zhí)行
setTimeout(function(){
console.log('hello!');
}, 0);
console.log('begin');
//begin hello
這段代碼的效果是表示當前代碼執(zhí)行完(執(zhí)行棧清空)以后,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)茁彭。
注意:
- 雖然代碼的本意是0毫秒后就推入事件隊列,但是html5標準中規(guī)定扶歪,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms理肺。
- 就算不等待4ms,就算假設0毫秒就推入事件隊列善镰,也會先執(zhí)行begin(因為只有主線程可執(zhí)行棧內(nèi)空了后才會主動讀取事件隊列)妹萨。要是當前代碼耗時很長,有可能要等很久炫欺,所以并沒有辦法保證乎完,回調(diào)函數(shù)一定會在setTimeout()指定的時間執(zhí)行。同理setInterval則是每次都精確的隔一段時間推入一個事件(但是品洛,事件的實際執(zhí)行時間不一定就準確树姨,還有可能是這個事件還沒執(zhí)行完畢,下一個事件就來了)
6.總結(jié)
這里我沒有總結(jié)css渲染這塊桥状,由于內(nèi)容較多帽揪,我會另開一篇文章來講解。
寫了這么多岛宦,還是要感謝大神們的文章台丛。