必須明白的瀏覽器渲染機制

前言

瀏覽器是咱們平常開發的重要的工具,那麼你瞭解瀏覽器嗎?即便在前端面試中,咱們也常常會遇到:在瀏覽器地址中從輸入url地址到出現頁面,這個過程發生了什麼?介紹一下重繪和迴流?這一類關於瀏覽器的問題。咱們可能會知道大概的輪廓但對於具體的細節倒是不那麼清楚,那麼今天咱們就從瀏覽器組成開始來了解一下瀏覽器的渲染機制javascript

瀏覽器組成

瀏覽器主要由7個部分組成:css

  • 用戶界面(User Interface):定義了一些經常使用的瀏覽器組件,好比地址欄,返回、書籤等等
  • 數據持久化(Data Persistence):指瀏覽器的cookie、local storage等組件
  • 瀏覽器引擎(Browser engine):平臺應用的相關接口,在用戶界面和呈現引擎之間傳送指令。
  • 渲染引擎(Rendering engine):處理HTML、CSS的解析與渲染
  • JavaScript解釋器(JavaScript Interpreter):解析和執行JavaScript代碼
  • 用戶界面後端(UI Backend):指瀏覽器的的圖形庫等
  • 網絡(Networking):用於網絡調用,好比HTTP請求

瀏覽器內核

瀏覽器內核分爲兩部分:渲染引擎(layout engineer或Rendering Engine)和JS引擎html

  • 渲染引擎:負責取得網頁的內容(HTML、XML、圖像等等)、整理訊息(例如加入CSS等),以及計算網頁的顯示方式,而後會輸出至顯示器或打印機
  • JS引擎:負責解析和執行javascript來實現網頁的動態效果 瀏覽器的內核的不一樣對於網頁的語法解釋會有不一樣,因此渲染的效果也不相同。全部網頁瀏覽器、電子郵件客戶端以及其它須要編輯、顯示網絡內容的應用程序都須要內核,最開始渲染引擎和JS引擎並無區分的很明確,後來JS引擎愈來愈獨立,內核就傾向於只指渲染引擎

常見的瀏覽器內核:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)前端

頁面加載流程

在瞭解瀏覽器渲染過程以前,先來了解一下頁面的加載流程。有助於更好理解後續渲染過程。從瀏覽器地址中從輸入url地址到渲染出一個頁面,會通過如下過程。 1.瀏覽器輸入的url地址通過DNS解析得到對應的IP 2.向服務器發起TCP的3次握手 3.創建連接後,瀏覽器向該IP地址發送http請求 4.服務器接收到請求,返回一堆 HMTL 格式的字符串代碼 5.瀏覽器得到html代碼,解析成DOM樹 6.獲取CSS並構建CSSOM 7.將DOM與CSSOM結合,建立渲染樹 8.找到全部內容都處於網頁的哪一個位置,佈局渲染樹 9.最終繪製出頁面java

瀏覽器渲染機制

咱們將要介紹的瀏覽器渲染過程主要步驟是5-9步,能夠用下面的圖來形象的展現 面試

image

解析HTML成DOM樹

這個解析過程大概能夠分爲幾個步驟: segmentfault

image
第一步:瀏覽器從磁盤或網絡讀取HTML的原始字節,也就是傳輸的0和1這樣的字節數據,並根據文件的指定編碼(例如 UTF-8)將它們轉換成字符串。 第二步:將字符串轉換成Token,例如:「」、「」等。Token中會標識出當前Token是「開始標籤」或是「結束標籤」亦或是「文本」等信息 第三步:在每一個Token被生成後,會馬上消耗這個Token建立出節點對象,所以在構建DOM的過程當中,不是等待全部的Token都生成後纔去構建DOM,而是一邊生成Token一邊消耗來生成節點對象。

注意:帶有結束標籤標識的Token不會建立節點對象 第四步:經過「開始標籤」與「結束標籤」來識別並關聯節點之間的關係。當全部Token都生成並消耗完畢後,咱們就獲得了一顆完整的DOM樹。後端

可是如今有一個疑問,節點之間的關聯關係是如何維護的呢? 上面咱們提到Token會標識是「開始標籤」仍是「結束標籤」,如下圖爲例:「Hello」Token位於「title」開始標籤與「title」結束標籤之間,代表「Hello」Token是「title」Token的子節點。同理「title」Token是「head」Token的子節點。 瀏覽器

image

構建CSSOM

既然有了html解析,那css解析也是必不可少的,解析css構建CSSOM 的過程和構建DOM的過程很是的類似。當瀏覽器接收到一段CSS,瀏覽器首先要作的是識別出Token,而後構建節點並生成CSSOM 緩存

image
節點中樣式能夠經過繼承獲得,也能夠本身設置,所以在構建的過程當中瀏覽器得遞歸 CSSOM 樹,而後肯定具體的元素究竟是什麼樣式。爲了CSSOM的完整性,也只有等構建完畢才能進入到下一個階段,哪怕DOM已經構建完,它也得等CSSOM,而後才能進入下一個階段。

CSS匹配HTML元素是一個至關複雜和有性能問題的事情。因此,DOM樹要小,CSS儘可能用id和class,千萬不要過渡層疊下去 因此,CSS的加載速度與構建CSSOM的速度將直接影響首屏渲染速度,所以在默認狀況下CSS被視爲阻塞渲染的資源

構建渲染樹

當咱們生成DOM樹和CSSOM樹後,咱們須要將這兩顆樹合併成渲染樹,在構建渲染樹的過程當中瀏覽器須要作以下工做:

  • 從 DOM 樹的根節點開始遍歷每一個可見節點。
  • 有些節點不可見(例如腳本Token、元Token等),由於它們不會體如今渲染輸出中,因此會被忽略。
  • 某些節點被CSS隱藏,所以在渲染樹中也會被忽略。例如某些節點設置了display: none屬性。
  • 對於每一個可見節點,爲其找到適配的 CSSOM 規則並應用它們

image

渲染阻塞

在渲染的過程當中,遇到一個script標記時,就會中止渲染,去請求腳本文件並執行腳本文件,由於瀏覽器渲染和 JS 執行共用一個線程,並且這裏必須是單線程操做,多線程會產生渲染 DOM 衝突。JavaScript的加載、解析與執行會嚴重阻塞DOM的構建。只有等到腳本文件執行完畢,纔會去繼續構建DOM。

js不單會阻塞DOM構建,還會致使CSSOM也阻塞DOM的構建,若是JavaScript腳本還操做了CSSOM,而正好這個CSSOM尚未下載和構建,瀏覽器甚至會延遲腳本執行和構建DOM,直至完成其CSSOM的下載和構建,而後再執行JavaScript,最後在繼續構建DOM

所以script的位置很重要,在實際使用過程當中遵循如下兩個原則:

  • CSS 優先:引入順序上,CSS 資源先於 JavaScript 資源。
  • JS置後:咱們一般把JS代碼放到頁面底部,且JavaScript 應儘可能少影響 DOM 的構建

佈局與繪製

瀏覽器拿到渲染樹後,就會從渲染樹的根節點開始遍歷,而後肯定每一個節點對象在頁面上的確切大小與位置,一般這一行爲也被稱爲「自動重排」。佈局階段的輸出是一個盒子模型,它會精確地捕獲每一個元素在屏幕內的確切位置與大小,全部相對測量值都將轉換爲屏幕上的絕對像素。這一過程也可稱爲迴流

佈局完成後,瀏覽器會當即發出「Paint Setup」和「Paint」事件,將渲染樹轉換成屏幕上的像素。

性能優化策略

在咱們瞭解瀏覽器的渲染機制後,DOM 和 CSSOM 結構構建順序,咱們能夠針對性能優化問題給出一些方案,提高頁面性能。

1.迴流(reflow)與重繪(repaint)

當元素的樣式發生變化時,瀏覽器須要觸發更新,從新繪製元素。這個過程當中,有兩種類型的操做,即重繪與迴流。

  • 重繪(repaint): 當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時因爲只須要UI層面的從新像素繪製,所以損耗較少
  • 迴流(reflow): 當元素的尺寸、結構或觸發某些屬性時,瀏覽器會從新渲染頁面,稱爲迴流。此時,瀏覽器須要從新通過計算,計算後還須要從新頁面佈局,所以是較重的操做。會觸發迴流的操做:
  • 添加或刪除可見的DOM元素
  • 元素的位置發生變化
  • 元素的尺寸發生變化(包括外邊距、內邊框、邊框大小、高度和寬度等)
  • 內容發生變化,好比文本變化或圖片被另外一個不一樣尺寸的圖片所替代。
  • 頁面一開始渲染的時候(這確定避免不了)
  • 瀏覽器的窗口尺寸變化(由於迴流是根據視口的大小來計算元素的位置和大小的

注意:迴流必定會觸發重繪,而重繪不必定會迴流,重繪的開銷較小,迴流的代價較高

所以爲了減小性能優化,咱們能夠儘可能避免迴流或者重繪操做 css

  • 避免使用table佈局
  • 將動畫效果應用到position屬性爲absolute或fixed的元素上

javascript

  • 避免頻繁操做樣式,可彙總後統一 一次修改
  • 儘可能使用class進行樣式修改
  • 減小dom的增刪次數,可以使用 字符串 或者 documentFragment 一次性插入
  • 極限優化時,修改樣式可將其display: none後修改
  • 避免屢次觸發上面提到的那些會觸發迴流的方法,能夠的話儘可能用 變量存住

async和defer的做用是什麼?有什麼區別?

defer 和 async 屬性的區別:

image
其中藍色線表明JavaScript加載;紅色線表明JavaScript執行;綠色線表明 HTML 解析 1)狀況1 <scriptsrc="script.js"> 沒有 defer 或 async,瀏覽器會當即加載並執行指定的腳本,也就是說不等待後續載入的文檔元素,讀到就加載並執行。

2)狀況2 (異步下載) async 屬性表示異步執行引入的 JavaScript,與 defer 的區別在於,若是已經加載好,就會開始執行——不管此刻是 HTML 解析階段仍是 DOMContentLoaded 觸發以後。須要注意的是,這種方式加載的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發以前或以後執行,但必定在 load 觸發以前執行。

3)狀況3 <scriptdefersrc="script.js">(延遲執行) defer 屬性表示延遲執行引入的 JavaScript,即這段 JavaScript 加載時 HTML 並未中止解析,這兩個過程是並行的。整個 document 解析完畢且 defer-script 也加載完成以後(這兩件事情的順序無關),會執行全部由 defer-script 加載的 JavaScript 代碼,而後觸發 DOMContentLoaded 事件。

defer 與相比普通 script,有兩點區別:

  • 載入 JavaScript 文件時不阻塞 HTML 的解析,執行階段被放到 HTML 標籤解析完成以後;
  • 在加載多個JS腳本的時候,async是無順序的加載,而defer是有順序的加載

js優化能夠在script標籤加上 defer屬性 和 async屬性用於在不阻塞頁面文檔解析的前提下,控制腳本的下載和執行

其餘: CSS 標籤的 rel屬性 中的屬性值設置爲 preload 可以讓你在你的HTML頁面中能夠指明哪些資源是在頁面加載完成後即刻須要的,最優的配置加載順序,提升渲染性能

首屏優化加載

  • 減小首屏CGI的計算量:好比在微信8.8無現金日H5開發中,前端但願拿到用戶的我的信息、消費記錄、排名三類數據,若是隻經過一個CGI來處理,那麼後臺響應時間確定會變長;因爲在H5的首屏中,只包含了用戶信息,消費記錄、排名都在第2屏和第3屏,此時其實能夠利用異步的方式來拿消費記錄、排名的數據。
  • 頁面瘦身:壓縮HTML、CSS、JavaScript。
  • 減小請求:CSS、JavaScript文件數儘可能少,甚至當CSS、JS的代碼很少時,能夠考慮直接將代碼內嵌到頁面中。
  • 多用緩存:緩存能大幅度下降頁面非首次加載的時間。
  • 少用table佈局,瀏覽器在渲染table時會消耗較多資源,並且只有table裏有一點變化,整個table都會從新渲染。
  • 作預加載:部分H5頁面首屏可能要下載較多的靜態資源,好比圖片,這時爲了不加載時出現「難看」的頁面,用預加載(loading的方式)作一個過渡

總結

咱們已經將瀏覽器的渲染機制瞭解了一遍,不只瞭解到一些性能優化方案,也能夠得出結論: 瀏覽器渲染的關鍵路徑共分五個步驟:

構建DOM -> 構建CSSOM -> 構建渲染樹 -> 佈局 -> 繪製

參考連接

相關文章
相關標籤/搜索