前言
瀏覽器基礎(chǔ)是前端知識網(wǎng)中的一個(gè)小分支溯香,也是前端開發(fā)人員必須掌握的基礎(chǔ)知識點(diǎn)。他貫穿著前端的整個(gè)網(wǎng)絡(luò)體系惠呼,項(xiàng)目優(yōu)化也是圍繞著瀏覽器進(jìn)行的滋早。
開發(fā)人員在面試的時(shí)候或許會被問到:
從你在瀏覽器輸入一個(gè)網(wǎng)址到網(wǎng)頁內(nèi)容完全被展示的這段時(shí)間內(nèi),都發(fā)生了什么事情考婴?
確實(shí)是個(gè)老生常談的問題贩虾,但問題的答案并不是唯一的,或許在三五年前沥阱,這個(gè)問題還會有一個(gè)「相對」標(biāo)準(zhǔn)的答案缎罢。
- 瀏覽器在接收到這個(gè)指令時(shí),會開啟一個(gè)單獨(dú)的線程來處理這個(gè)指令考杉,首先要判斷用戶輸入的是否為合法或合理的 URL 地址屁使,是否為 HTTP 協(xié)議請求,如果是那就進(jìn)入下一步
- 瀏覽器的瀏覽器引擎將對此 URL 進(jìn)行分析奔则,如果存在緩存「cache-control」且未過期蛮寂,則會從本地緩存提取文件(From Memory Cache,200返回碼)易茬,如果緩存「cache-control」不存在或過期酬蹋,瀏覽器將發(fā)起遠(yuǎn)程請求
- 通過 DNS 解析域名獲取該網(wǎng)站地址對應(yīng)的 IP 地址,連同瀏覽器的 Cookie抽莱、 userAgent 等信息向此 IP 發(fā)出 GET 請求范抓。
- 接下來就是經(jīng)典的「三次握手」,HTTP 協(xié)議會話食铐,瀏覽器客戶端向 Web 服務(wù)器發(fā)送報(bào)文匕垫,進(jìn)行通訊和數(shù)據(jù)傳輸。
- 進(jìn)入網(wǎng)站的后端服務(wù)虐呻,如 Tomcat象泵、Apache 等寞秃,還有近幾年流行的 Node.js 服務(wù)器,這些服務(wù)器上部署著應(yīng)用代碼偶惠,語言有很多春寿,如 Java、 PHP忽孽、 C++绑改、 C# 和 Javascript 等。
- 服務(wù)器根據(jù) URL 執(zhí)行相應(yīng)的后端應(yīng)用邏輯兄一,期間會使用到「服務(wù)器緩存」或「數(shù)據(jù)庫」厘线。
- 服務(wù)器處理請求并返回響應(yīng)報(bào)文,如果瀏覽器訪問過該頁面出革,緩存上有對應(yīng)資源皆的,與服務(wù)器最后修改記錄對比,一致則返回 304蹋盆,否則返回 200 和對應(yīng)的內(nèi)容费薄。
- 瀏覽器接收到返回信息并開始下載該 HTML文件(無緩存、200返回碼)或從本地緩存提取文件(有緩存栖雾、304返回碼)
- 瀏覽器的渲染引擎在拿到 HTML 文件后楞抡,便開始解析構(gòu)建 DOM 樹,并根據(jù) HTML 中的標(biāo)記請求下載指定的 MIME 類型文件(如 CSS析藕、 JavaScript 腳本等)召廷,同時(shí)使用&設(shè)置緩存等內(nèi)容。
- 渲染引擎根據(jù) CSS 樣式規(guī)則將 DOM 樹擴(kuò)充為渲染樹账胧,然后進(jìn)行重排竞慢、重繪。
- 如果含有 JS 文件將會執(zhí)行治泥,進(jìn)行 Dom 操作筹煮、緩存讀存、事件綁定等操作居夹。最終頁面將被展示在瀏覽器上败潦。
此答案精簡的概括了「后端為主的 MVC 模式」及早期 Web 應(yīng)用的瀏覽器響應(yīng)的全過程。前端技術(shù)發(fā)展到現(xiàn)在准脂,「前后端分離」「中間件直出」和「MNV*模式」也已問世劫扒,再談及此問題,答案會有所不同狸膏。
就以「前后端分離」為例沟饥,在上方答案的第4步后,緊接著就不會直接進(jìn)入后端服務(wù)器了。而會被 HTTP 和反向代理服務(wù)器贤旷,如 Ngnix广料,攔截。
- 前置步驟1遮晚、2、3拦止、4
- Ngnix 在監(jiān)聽到 HTTP(80端口)或 HTTPS(443端口)請求县遣,根據(jù) URL 做服務(wù)分發(fā),分發(fā)(rewrite)到后端服務(wù)器或靜態(tài)資源服務(wù)器汹族,首頁請求基本是分發(fā)到靜態(tài)服務(wù)器萧求,返回一個(gè) HTML 文件
- 步驟7、8顶瞒、9夸政、10
- 執(zhí)行 JS 腳本,異步 ajax榴徐、 fetch 發(fā)起 POST守问、 GET 請求,重新進(jìn)入 Ngnix 分發(fā)坑资,此次分發(fā)到后端服務(wù)器耗帕,步驟5、6袱贮、7仿便,然后返回一個(gè) xml 或 json 格式的信息,一般含有 code(返回碼)和 result(依賴信息)
- js 回調(diào)根據(jù)返回碼執(zhí)行不同的邏輯攒巍,增刪改頁面元素嗽仪,此時(shí)可能會發(fā)生重排或重繪。首頁加載結(jié)束柒莉。
從以上步驟可以發(fā)現(xiàn)闻坚,瀏覽器可能會觸發(fā)兩次重繪,極易產(chǎn)生「白屏」或「頁面抖動」現(xiàn)象兢孝,為了解決這個(gè)問題「中間件直出」的模式應(yīng)運(yùn)而生鲤氢。另外為了擴(kuò)充大前端的陣營,吸納 IOS 和 Android西潘,Google 設(shè)計(jì)了「MNV*模式」卷玉,典型代表就是 ReactNative,但此模式已經(jīng)脫離了瀏覽器的范疇喷市,此處就不再做擴(kuò)展相种。
以上討論的渲染過程中使用到了較多的瀏覽器功能,如用戶地址欄輸入框、網(wǎng)絡(luò)請求寝并、瀏覽器文檔解析箫措、渲染引擎渲染網(wǎng)頁、 JavaScript 引擎執(zhí)行 js 腳本衬潦、客戶端存儲等斤蔓。 接下來我們介紹下瀏覽器的基本結(jié)構(gòu)組成。
瀏覽器的結(jié)構(gòu)組成
瀏覽器一般由七個(gè)模塊組成镀岛,User Interface(用戶界面)弦牡、Browser engine(瀏覽器引擎)、Rendering engine(渲染引擎)漂羊、Networking(網(wǎng)絡(luò))驾锰、JavaScript Interpreter(js解釋器)、UI Backend(UI 后端)走越、Date Persistence(數(shù)據(jù)持久化存儲) 如下圖:
- 用戶界面 -包括地址欄椭豫、后退/前進(jìn)按鈕、書簽?zāi)夸浀戎贾福簿褪悄闼吹降某隧撁骘@示窗口之外的其他部分
- 瀏覽器引擎 -可以在用戶界面和渲染引擎之間傳送指令或在客戶端本地緩存中讀寫數(shù)據(jù)等赏酥,是瀏覽器中各個(gè)部分之間相互通信的核心
- 渲染引擎 -解析DOM文檔和CSS規(guī)則并將內(nèi)容排版到瀏覽器中顯示有樣式的界面,也有人稱之為排版引擎谆构,我們常說的瀏覽器內(nèi)核主要指的就是渲染引擎
- 網(wǎng)絡(luò) -用來完成網(wǎng)絡(luò)調(diào)用或資源下載的模塊
- UI 后端 -用來繪制基本的瀏覽器窗口內(nèi)控件今缚,如輸入框、按鈕低淡、單選按鈕等姓言,根據(jù)瀏覽器不同繪制的視覺效果也不同,但功能都是一樣的蔗蹋。
- JS解釋器 -用來解釋執(zhí)行JS腳本的模塊何荚,如 V8 引擎、JavaScriptCore
- 數(shù)據(jù)存儲 -瀏覽器在硬盤中保存 cookie猪杭、localStorage等各種數(shù)據(jù)餐塘,可通過瀏覽器引擎提供的API進(jìn)行調(diào)用
作為前端開發(fā)人員,我們需要重點(diǎn)理解渲染引擎的工作原理皂吮,靈活應(yīng)用數(shù)據(jù)存儲技術(shù)戒傻,在實(shí)際項(xiàng)目開發(fā)中會經(jīng)常涉及到這兩個(gè)部分,尤其是在做項(xiàng)目性能優(yōu)化時(shí)蜂筹,理解瀏覽器渲染引擎的工作原理尤為重要需纳。而其他部分則是由瀏覽器自行管理的,開發(fā)者能控制的地方較少艺挪。今天我們就圍繞這兩個(gè)重點(diǎn)其中的一個(gè)部分「瀏覽器渲染引擎」進(jìn)行展開
瀏覽器渲染引擎
瀏覽器渲染引擎是由各大瀏覽器廠商依照 W3C 標(biāo)準(zhǔn)自行研發(fā)的不翩,也被稱之為「瀏覽器內(nèi)核」。
目前,市面上使用的主流瀏覽器內(nèi)核有5類:Trident口蝠、Gecko器钟、Presto、Webkit妙蔗、Blink傲霸。
Trident:俗稱 IE 內(nèi)核,也被叫做 MSHTML 引擎眉反,目前在使用的瀏覽器有 IE11 -昙啄,以及各種國產(chǎn)多核瀏覽器中的IE兼容模塊。另外微軟的 Edge 瀏覽器不再使用 MSHTML 引擎禁漓,而是使用類全新的引擎 EdgeHTML跟衅。
Gecko:俗稱 Firefox 內(nèi)核孵睬,Netscape6 開始采用的內(nèi)核播歼,后來的 Mozilla FireFox(火狐瀏覽器)也采用了該內(nèi)核,Gecko 的特點(diǎn)是代碼完全公開掰读,因此秘狞,其可開發(fā)程度很高,全世界的程序員都可以為其編寫代碼蹈集,增加功能烁试。因?yàn)檫@是個(gè)開源內(nèi)核,因此受到許多人的青睞拢肆,Gecko 內(nèi)核的瀏覽器也很多减响,這也是 Gecko 內(nèi)核雖然年輕但市場占有率能夠迅速提高的重要原因。
Presto:Opera 前內(nèi)核郭怪,為啥說是前內(nèi)核呢支示?因?yàn)?Opera12.17 以后便擁抱了 Google Chrome 的 Blink 內(nèi)核,此內(nèi)核就沒了寄托
Webkit:Safari 內(nèi)核鄙才,也是 Chrome 內(nèi)核原型颂鸿,主要是 Safari 瀏覽器在使用的內(nèi)核,也是特性上表現(xiàn)較好的瀏覽器內(nèi)核攒庵。也被大量使用在移動端瀏覽器上嘴纺。
Blink: 由 Google 和 Opera Software 開發(fā),在Chrome(28及往后版本)浓冒、Opera(15及往后版本)和Yandex瀏覽器中使用栽渴。Blink 其實(shí)是 Webkit 的一個(gè)分支,添加了一些優(yōu)化的新特性稳懒,例如跨進(jìn)程的 iframe熔萧,將 DOM 移入 JavaScript 中來提高 JavaScript 對 DOM 的訪問速度等,目前較多的移動端應(yīng)用內(nèi)嵌的瀏覽器內(nèi)核也漸漸開始采用 Blink。
渲染引擎的工作流程
瀏覽器渲染引擎最重要的工作就是將 HTML 和 CSS 文檔解析組合最終渲染到瀏覽器窗口上佛致。如下圖所示贮缕,渲染引擎在接受到 HTML 文件后主要進(jìn)行了以下操作:解析 HTML 構(gòu)建 DOM 樹 -> 構(gòu)建渲染樹 -> 渲染樹布局 -> 渲染樹繪制。
解析 HTML 構(gòu)建 DOM 樹時(shí)渲染引擎會將 HTML 文件的便簽元素解析成多個(gè) DOM 元素對象節(jié)點(diǎn)俺榆,并且將這些節(jié)點(diǎn)根據(jù)父子關(guān)系組成一個(gè)樹結(jié)構(gòu)感昼。同時(shí) CSS 文件被解析成 CSS 規(guī)則表,然后將每條 CSS 規(guī)則按照「從右向左」的方式在 DOM 樹上進(jìn)行逆向匹配罐脊,生成一個(gè)具有樣式規(guī)則描述的 DOM 渲染樹定嗓。接下來就是將渲染樹進(jìn)行布局、繪制的過程萍桌。首先根據(jù) DOM 渲染樹上的樣式規(guī)則宵溅,對 DOM 元素進(jìn)行大小和位置的定位,關(guān)鍵屬性如position;width;margin;padding;top;border;...
上炎,接下來再根據(jù)元素樣式規(guī)則中的color;background;shadow;...
規(guī)則進(jìn)行繪制恃逻。
另外,這個(gè)過程是逐步完成的藕施,為了更好的用戶體驗(yàn)寇损,渲染引擎將會盡可能早的將內(nèi)容呈現(xiàn)到屏幕上,并不會等到所有的 html 都解析完成之后再去構(gòu)建和布局 render 樹裳食。它是解析完一部分內(nèi)容就顯示一部分內(nèi)容矛市,同時(shí),可能還在通過網(wǎng)絡(luò)下載其余內(nèi)容诲祸。
再者浊吏,需要注意的是,在瀏覽器渲染完首屏頁面后救氯,如果對 DOM 進(jìn)行操作會引起瀏覽器引擎對 DOM 渲染樹的重新布局和重新繪制找田,我們叫做「重排」和「重繪」,由于重排和重繪是前后依賴的關(guān)系径密,重繪發(fā)生時(shí)未必會觸發(fā)渲染引擎的重排午阵,但是如果發(fā)生了重排就必然會觸發(fā)重繪操作,這樣帶來的性能損害就是巨大的享扔。因此我們在做性能優(yōu)化的時(shí)候應(yīng)該遵循「避免重排底桂;減少重繪」的原則。
不同瀏覽器內(nèi)核間的差異
在不同的瀏覽器內(nèi)核下惧眠, 瀏覽器頁面渲染的流程略有不同
上面兩幅圖分別是 Webkit 和 Geoko 內(nèi)核渲染 DOM 的工作流程籽懦,對比可以看出,兩者的區(qū)別主要在于 CSS 樣式表的解析時(shí)機(jī)氛魁,Webkit 內(nèi)核下暮顺,HTML 和 CSS 文件的解析是同步的厅篓,而 Geoko 內(nèi)核下,CSS 文件需要等到 HTML 文件解析成內(nèi)容 Sink 后才進(jìn)行解析捶码。
另外描述術(shù)語也有不同羽氮,除此之外兩者的流程就基本相同了,其中最重要的三個(gè)部分就是 「HTML 的解析」「CSS 的解析」「渲染樹的生成」惫恼。這三個(gè)部分的原理比較深档押,會涉及到「詞法分析」「語法分析」「轉(zhuǎn)換」「解釋」等數(shù)據(jù)結(jié)構(gòu)的知識,比較枯燥祈纯,一般我們了解到這里就夠了令宿,想深入了解的同學(xué)可以閱讀此篇譯文,瀏覽器的工作原理腕窥,里面詳細(xì)的解釋了以上三個(gè)部分的流程和原理粒没。此處就不再多做贅述了。
關(guān)于 CSS 規(guī)則的匹配
上面我們提到過簇爆, CSS 規(guī)則是按照「從右向左」的方式在 DOM 樹上進(jìn)行逆向匹配的癞松,最終生成一個(gè)具有樣式規(guī)則描述的 DOM 渲染樹。
但是你知道為什么要「從右向左」做逆向匹配嗎冕碟?
我們重新回看【webkit 內(nèi)核工作流程圖】
CSS 規(guī)則匹配是發(fā)生在webkit引擎的「Attachment」過程中拦惋,瀏覽器要為每個(gè) DOM Tree 中的元素?cái)U(kuò)充 CSS 樣式規(guī)則(匹配 Style Rules)匆浙。對于每個(gè) DOM 元素安寺,必須在所有 Style Rules 中找到符合的 selector 并將對應(yīng)的規(guī)則進(jìn)行合并。選擇器的「解析」實(shí)際是在這里執(zhí)行的首尼,在遍歷 DOM Tree 時(shí)挑庶,從 Style Rules 中去尋找對應(yīng)的 selector。
我們來舉一個(gè)最簡單的栗子:
<template>
<div>
<div class="t">
<span>test</span>
<p>test</p>
<div>
</div>
</template>
<style>
div{ color: #000; }
div .t span{ color: red; }
div .t p{color: blue; }
</style>
此處我們有一個(gè) html 元素 和一個(gè) style 元素软能,兩者需要做遍歷匹配
此處會有 4*3 個(gè)匹配項(xiàng)迎捺,如果做正向匹配,在遇到 <span>
標(biāo)簽匹配 div .t p{ color: red; }
到匹配項(xiàng)時(shí)查排,計(jì)算機(jī)首先要找到<span>
標(biāo)簽的父標(biāo)簽和祖父標(biāo)簽凳枝,判斷他們是否滿足div .t
的規(guī)則,然后再匹配<span>
是否為p
標(biāo)簽跋核,此處匹配不成功岖瑰,產(chǎn)生了三次浪費(fèi)。
如果時(shí)逆向匹配砂代,那么第一次對比<span>
是否為p
標(biāo)簽便可排除此規(guī)則蹋订,效率更高。
如果將 HTML 結(jié)構(gòu)變復(fù)雜刻伊,CSS 規(guī)則表變龐大露戒,那么椒功,「逆向匹配」的優(yōu)勢就遠(yuǎn)大于「正向匹配」了,因?yàn)槠ヅ涞那闆r遠(yuǎn)遠(yuǎn)低于不匹配的情況智什。另外动漾,如果在選擇器結(jié)尾加上通配符「*」,那么「逆向匹配」的優(yōu)勢就大打折扣了荠锭,這也就是很多優(yōu)化原則提到的「盡量避免在選擇器末尾添加通配符」的原因谦炬。
極限了想,如果我們的樣式表不存在嵌套關(guān)系节沦,如下:
<template>
<div class="t">
<span class="div_t_span">test</span>
<p class="div_t_p">test</p>
<div>
</template>
<style>
div{ color: #000; }
.div_t_span{ color: red; }
.div_t_p{color: blue; }
</style
那么引擎的「Attachment」過程將得到極大的精簡键思,效率也是可想而知的,這就是為什么「微信小程序」樣式表不建議使用關(guān)系行寫法的原因甫贯。
相關(guān)的性能優(yōu)化
我們大致可以在以上案例中看到同瀏覽器渲染引擎相關(guān)的可行優(yōu)化點(diǎn)吼鳞。
大致為以下幾種
減少 JS 加載對 Dom 渲染的影響
將 JS 文件放在 HTML 文檔后加載,或者使用異步的方式加載 JS 代碼
避免重排叫搁,減少重繪
在做 css 動畫的時(shí)候減少使用 width赔桌、 margin、 padding 等影響 CSS 布局對規(guī)則渴逻,可以使用 CSS3 的 transform 代替疾党。另外值得注意的是,在加載大量的圖片元素時(shí)惨奕,盡量預(yù)先限定圖片的尺寸大小雪位,否則在圖片加載過程中會更新圖片的排版信息,產(chǎn)生大量的重排梨撞。
減少使用關(guān)系型樣式表的寫法
直接使用唯一的類名即可最大限度的提升渲染效率雹洗,另外盡量避免在選擇器末尾添加通配符
減少 DOM 的層級
減少無意義的 dom 層級可以減少 渲染引擎 Attachment 過程中的匹配計(jì)算量