瀏覽器渲染機(jī)制簡(jiǎn)括

原文作者:IQ前端 公號(hào) / LazyCarry

前言

前端開發(fā)每天都要跟瀏覽器打交道怜俐,那么瀏覽器到底是怎樣渲染的呢?

我們都知道吞滞,JS是單線程的佑菩,也就是只有前一個(gè)任務(wù)執(zhí)行完成盾沫,才會(huì)執(zhí)行下一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng)殿漠,那么下一個(gè)任務(wù)就只能干等著赴精。顯然,這樣是非常浪費(fèi)資源的绞幌。那么就要解決這個(gè)問題啦蕾哟,先來了解一下「Event Loop」事件循環(huán)。

Event Loop

我們先來看一下HTML標(biāo)準(zhǔn)的解釋:為了協(xié)調(diào)事件event莲蜘,用戶交互user interaction谭确,腳本script,渲染rendering票渠,網(wǎng)絡(luò)networking等逐哈,用戶代理user agent必須使用事件循環(huán)「Event Loop」

「event-loop」是解決JS單線程運(yùn)行阻塞的一種機(jī)制问顷,在JS的異步運(yùn)行機(jī)制中昂秃,我們需要知道:

  • 所有的「同步任務(wù)」都在主線程進(jìn)行
  • 「異步任務(wù)」進(jìn)入任務(wù)隊(duì)列,任務(wù)隊(duì)列會(huì)通知主線程杜窄,哪個(gè)異步任務(wù)可以執(zhí)行肠骆,這個(gè)異步任務(wù)就會(huì)進(jìn)入主線程。異步任務(wù)必須指定回調(diào)函數(shù)塞耕,當(dāng)主線程開始執(zhí)行異步任務(wù)蚀腿,其實(shí)就是在執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
  • 如果主線程的所有同步任務(wù)都執(zhí)行完扫外,系統(tǒng)就會(huì)去讀取「任務(wù)隊(duì)列」上的異步任務(wù)莉钙,如果有可以執(zhí)行的,就會(huì)結(jié)束等待狀態(tài)畏浆,進(jìn)入主線程胆胰,開始執(zhí)行。
  • 主線程不斷的執(zhí)行第3步
    這就是JS的運(yùn)行機(jī)制刻获,也稱為「Event Loop」事件循環(huán)

1.異步任務(wù)分類

  • 「宏任務(wù)」macrotasks:script(整體代碼)瞎嬉、setTimeout蝎毡、setInterval、setImmediate氧枣、I/O沐兵、UI rendering
  • 「微任務(wù)」microtasks:process.nextTick(NodeJS)、Promise.then()便监、Object.observe扎谎、MutationObserver
    注意:瀏覽器在執(zhí)行異步任務(wù)的時(shí)候會(huì)優(yōu)先執(zhí)行微任務(wù)碳想。

2.理解事件循環(huán)

console.log('script start')

setTimeout(() => {
  console.log('setTimeout')
}, 0)js

Promise.resolve().then(() => {
  console.log('Promise 1')
}).then(() => {
  console.log('Promise 2')
})

console.log('script end')

//運(yùn)行結(jié)果
//script start
//script end
//Promise 1
//Promise 2
//setTimeout

過程分析:

1、task隊(duì)列中只有script毁靶,則將script中所有函數(shù)放進(jìn)函數(shù)執(zhí)行棧按順序執(zhí)行
2胧奔、接下來遇到setTimeout,這里的setTimeout只是將回調(diào)函數(shù)在0毫秒后放入task隊(duì)列(其實(shí)是4ms预吆,html5標(biāo)準(zhǔn)中規(guī)定中要求setTimeout中低于4ms的時(shí)間間隔算為4ms)龙填,也就是說回調(diào)函數(shù)在下一個(gè)事件循環(huán)執(zhí)行,setTimeout在這里將回調(diào)函數(shù)放至task列表后就結(jié)束了拐叉。
3岩遗、遇到Promise,屬于「microtasks」凤瘦,所以將第一個(gè)then的回調(diào)放到microtasks隊(duì)列
4宿礁、執(zhí)行完所有script代碼后,檢查microtasks隊(duì)列蔬芥,發(fā)現(xiàn)隊(duì)列不為空梆靖,所以執(zhí)行第一個(gè)回調(diào)函數(shù)輸出'Promise 1',由于then的返回仍然是Promise坝茎,因此將第二個(gè)then的回調(diào)放至microtasks隊(duì)列并執(zhí)行輸出'Promise 1' 5涤姊、此時(shí)microtasks隊(duì)列為空,開始執(zhí)行下一個(gè)事件循環(huán)嗤放。
5.檢查task隊(duì)列發(fā)現(xiàn)setTimeout的回調(diào)函數(shù)思喊,因此執(zhí)行輸出'setTimeout'
注意:promise本身他是一個(gè)同步任務(wù),只不過他返回出來的resolvereject是異步任務(wù),或者說.then或者.catch的為異步任務(wù)。

瀏覽器渲染

上面我們已經(jīng)大致的了解到事件循環(huán)次酌,為什么講瀏覽器渲染要扯到事件循環(huán)呢恨课?這是因?yàn)槭录h(huán)跟瀏覽器渲染有大大的關(guān)系,我們來一探究竟岳服。

1.關(guān)鍵渲染路徑

關(guān)鍵渲染路徑指的是瀏覽器接收最初的HTML剂公,CSS,JS等資源后吊宋,解析纲辽,構(gòu)建樹,渲染布局璃搜,繪制拖吼,最后呈現(xiàn)給用戶能看到的界面的這個(gè)過程。主要過程如下:

  • 解析HTML生成DOM樹
  • 解析CSS生成CSSOM規(guī)則樹(CSS Object Model)
  • 將DOM樹和CSSO規(guī)則樹合并生成渲染樹(rendering tree)
  • 遍歷渲染樹開始布局这吻,計(jì)算每個(gè)節(jié)點(diǎn)的位置大小等信息
  • 將渲染樹每個(gè)節(jié)點(diǎn)繪制到屏幕

上面的5個(gè)步驟并不是一次性執(zhí)行完成吊档,例如DOM或者CSSOM被修改時(shí),就會(huì)有某個(gè)過程需要被重復(fù)執(zhí)行唾糯,重新計(jì)算并重新渲染怠硼。
注意:從上面的第3步可以看出鬼贱,css是會(huì)影響到頁(yè)面的渲染的,即若css報(bào)錯(cuò)香璃,他是會(huì)影響到瀏覽器頁(yè)面的生成的这难。

2.構(gòu)建DOM樹

當(dāng)瀏覽器收到HTML文檔后,會(huì)遍歷文檔節(jié)點(diǎn)增显,生成DOM樹雁佳。HTML Parser將HTML標(biāo)記解析成DOM樹。

3.構(gòu)建CSSOM規(guī)則樹

CSS Parser將每個(gè)CSS文件都解析成一個(gè)樣式表「StyleSheet」對(duì)象同云,每個(gè)對(duì)象都包含樣式規(guī)則「Style Rules」糖权,也叫做CSSOM

4.構(gòu)建渲染樹(Render Tree)

有了DOM樹跟CSS規(guī)則樹炸站,瀏覽器就可以結(jié)合他們來構(gòu)建渲染「render」樹了星澳。瀏覽器會(huì)先從DOM樹的根節(jié)點(diǎn)開始遍歷每個(gè)可見節(jié)點(diǎn)掀抹,然后為每個(gè)可見節(jié)點(diǎn)找到適配的CSS樣式規(guī)則并應(yīng)用到DOM樹上躁染。

但是DOM樹跟渲染樹在結(jié)構(gòu)上又不是完全對(duì)應(yīng)的痴奏,區(qū)別在于:

  • display:none的元素不在渲染樹中(因?yàn)樗N毀了該元素)
  • visibility:hidden的元素在渲染樹中(因?yàn)樗皇前言撛仉[藏了)

5.渲染樹布局

生成渲染樹之后狼电,還是沒有辦法直接渲染到屏幕上。因?yàn)檫@時(shí)候還不知道每一個(gè)節(jié)點(diǎn)的位置信息蹋订,這就需要布局(Layout)的處理了赔退,這個(gè)過程其實(shí)就是根據(jù)渲染樹中渲染對(duì)象的信息橄维,計(jì)算出每一個(gè)渲染對(duì)象的位置跟尺寸忌堂,將渲染對(duì)象放在瀏覽器相應(yīng)的位置上盒至。

6.回流與重繪

回流(reflow):當(dāng)瀏覽器發(fā)現(xiàn)某個(gè)部分發(fā)生改變影響了布局,需要重新渲染士修。回流會(huì)從html的root frame開始遞歸往下枷遂,依次計(jì)算所有節(jié)點(diǎn)的尺寸跟位置。回流幾乎是無法避免的棋嘲,只要行為引起了頁(yè)面上元素的占位方式酒唉,定位方式,邊距等屬性的變化沸移,這都會(huì)引起內(nèi)部痪伦,周圍,甚至整個(gè)頁(yè)面的重新渲染雹锣。

重繪(repaint):當(dāng)改變某個(gè)元素的背景顏色流妻,文字顏色,邊框顏色等不影響它內(nèi)部以及周圍布局的笆制。屏幕的某一部分要重畫,但是元素的尺寸位置都沒有改變涣达,這就是重繪在辆。

  • display:none會(huì)出發(fā)回流证薇,位置發(fā)生了變化。而visibility:hidden只會(huì)觸發(fā)重繪匆篓,位置沒有變浑度。
  • 有些時(shí)候,修改了元素的尺寸或者顏色鸦概,瀏覽器不會(huì)立即回流或者重繪一次箩张,而是會(huì)將這些操作積累下來,然后再做一次reflow窗市,這叫做異步reflow先慷。
  • 瀏覽器也有立即進(jìn)行回流的情況,例如resize窗口咨察,改變頁(yè)面默認(rèn)字體等论熙。

瀏覽器渲染進(jìn)程

瀏覽器的渲染是多進(jìn)程的,包含了Browser進(jìn)程摄狱,第三方插件進(jìn)程脓诡,GPU進(jìn)程瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核)媒役,這里我們重點(diǎn)分析瀏覽器渲染進(jìn)程祝谚,因?yàn)轫?yè)面的渲染,JS的執(zhí)行酣衷,事件的觸發(fā)都是在這個(gè)進(jìn)程中進(jìn)行的交惯。
劃重點(diǎn):「瀏覽器的渲染是多進(jìn)程的,瀏覽器的渲染進(jìn)程是多線程的鸥诽∩堂担」

瀏覽器渲染進(jìn)程有多個(gè)線程,下面來介紹瀏覽器的線程與其作用:

1.GUI渲染進(jìn)程

GUI渲染進(jìn)程做的事情其實(shí)就是上述的「關(guān)鍵路徑渲染」牡借,這里將不再敘述拳昌。而在了解GUI渲染進(jìn)程的執(zhí)行過程后,我們可以根據(jù)原理進(jìn)行渲染優(yōu)化:

  • 盡早引入CSS文件钠龙,例如在頭部引入
  • 盡可能早的加載在CSS文件引入資源炬藤,例如自定義文件〔昀铮可以使用預(yù)加載
  • 在DOM和CSS渲染后加入JS文件沈矿,例如可以在尾部加載JS文件

2.JS引擎線程

JS引擎線程,也稱為JS內(nèi)核咬腋,負(fù)責(zé)處理JavaScript腳本程序羹膳。JS引擎等待著任務(wù)隊(duì)列任務(wù)的到來,然后處理這些任務(wù)根竿。無論什么時(shí)候陵像,都只有一個(gè)JS引擎線程就珠,因?yàn)镴S是單線程的。

關(guān)于為什么JS是單線程的醒颖,這里我想用一個(gè)例子來解釋一下:假如JS是多線程的妻怎,假設(shè)現(xiàn)在有2條線程,一條在dom節(jié)點(diǎn)上添加節(jié)點(diǎn)泞歉,另一條刪除這個(gè)節(jié)點(diǎn)逼侦。那么問題來了,這時(shí)候該以那條線程為準(zhǔn)腰耙。所以說榛丢,JS的主要用途就是與用戶互動(dòng),操作dom節(jié)點(diǎn)沟优,這就決定了JS只能是單線程的涕滋。

3.事件觸發(fā)線程
事件觸發(fā)線程用來控制事件循環(huán),當(dāng)對(duì)應(yīng)的事件符合條件被觸發(fā)時(shí)挠阁,該線程會(huì)將事件添加到待處理的事件隊(duì)列中宾肺,等待JS引擎的處理。

上述已經(jīng)講到侵俗,所有的同步任務(wù)都在主線程運(yùn)行锨用,而異步任務(wù)進(jìn)入任務(wù)隊(duì)列。而異步任務(wù)均由事件觸發(fā)線程控制隘谣,只要異步任務(wù)有了運(yùn)行結(jié)果增拥,就會(huì)在任務(wù)隊(duì)列中放置回調(diào)函數(shù),所以說異步任務(wù)一定要指定回調(diào)函數(shù)寻歧。

主線程空了掌栅,就會(huì)去讀取任務(wù)隊(duì)列。這個(gè)過程不斷的重復(fù)码泛,其本質(zhì)基于JS的事件輪詢機(jī)制猾封。

4.定時(shí)器觸發(fā)線程

JS是單線程的,當(dāng)處于阻塞線程的狀態(tài)會(huì)影響計(jì)時(shí)的準(zhǔn)確性噪珊,因此需要單獨(dú)開一個(gè)線程來計(jì)時(shí)晌缘。

當(dāng)使用setTimeout或者setInterval時(shí),需要定時(shí)器線程計(jì)時(shí)痢站。計(jì)時(shí)完成后會(huì)將特定的事件推進(jìn)事件觸發(fā)線程的任務(wù)隊(duì)列中磷箕,等待進(jìn)入主線程執(zhí)行。

5.異步http請(qǐng)求線程

XMLHttpRequest在連通后通過瀏覽器新起一個(gè)線程請(qǐng)求
檢測(cè)到狀態(tài)變化時(shí)阵难,如果有設(shè)置回調(diào)函數(shù)岳枷,異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中,再由JS引擎執(zhí)行嫩舟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末氢烘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子家厌,更是在濱河造成了極大的恐慌,老刑警劉巖椎工,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭于,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡维蒙,警方通過查閱死者的電腦和手機(jī)掰吕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颅痊,“玉大人殖熟,你說我怎么就攤上這事“呦欤” “怎么了菱属?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舰罚。 經(jīng)常有香客問我纽门,道長(zhǎng),這世上最難降的妖魔是什么营罢? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任赏陵,我火速辦了婚禮,結(jié)果婚禮上饲漾,老公的妹妹穿的比我還像新娘蝙搔。我一直安慰自己,他們只是感情好考传,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布吃型。 她就那樣靜靜地躺著,像睡著了一般伙菊。 火紅的嫁衣襯著肌膚如雪败玉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天镜硕,我揣著相機(jī)與錄音运翼,去河邊找鬼。 笑死兴枯,一個(gè)胖子當(dāng)著我的面吹牛血淌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悠夯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼癌淮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沦补,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤乳蓄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后夕膀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虚倒,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年产舞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了魂奥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡易猫,死狀恐怖耻煤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情准颓,我是刑警寧澤哈蝇,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瞬场,受9級(jí)特大地震影響买鸽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贯被,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一眼五、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彤灶,春花似錦看幼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搏熄,卻和暖如春棚唆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背心例。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工宵凌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人止后。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓瞎惫,卻偏偏與公主長(zhǎng)得像溜腐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓜喇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容