從瀏覽器渲染原理談性能優化(2017版)

隨着技術的革新,我們有了越來越多的方案去提升頁面性能,因此在2019年進行了本篇文章的更新。更新內容包括:
1、網絡通信部分的細節化,包括:

  • 網絡時延、tcp簡述
  • HTTP個協議間差別及如何對應優化
  • HTTP2將對原有優化方案存在那些影響等

2、性能優化方案的增加與說明,包括:

  1. 資源預取
  2. 通用優化方案
  3. 針對頁面渲染規則的優化方案(js、css)
  4. 如何藉助chrome進行性能分析與優化
  5. 附錄

3、附錄:相關博客推薦

如果你對這些細節更感興趣,請移步
https://blog.csdn.net/riddle1981/article/details/90756332

而如果你更關心瀏覽器渲染原理而非性能優化方案,以下爲正文:


前言

  以前學習瀏覽器的渲染機制時,對瀏覽器的渲染概念就是html解析成DOM,css形成樣式規則。兩者共同構建渲染樹。瀏覽器根據渲染樹的樣式進行佈局和渲染。後來再次回過頭去看這些概念時發現很多知識點都是非常深的。   比如瀏覽器如何解析CSS形成樣式樹,那麼瀏覽器究竟如何解析?瞭解這些並非沒有意義,比如當了解css解析是自右向左後,就知道在寫css樣式時應該避免嵌套。瞭解解析順序就知道如何縮短首屏時間提高用戶體驗。所以決定將這段時間的所學整理出來形成知識體系。有些的不足和不到位的地方還請大家不吝指正或提出建議

一、關於瀏覽器渲染的容易誤解點總結

關於瀏覽器渲染機制已經是老生常談,如果你想了解[請點這裏](http://blog.csdn.net/riddle1981/article/details/76380177),而且網上現有資料中有非常多的優秀資料對此進行闡述。遺憾的是網上的資料良莠不齊,經常在不同的文檔中對同一件事的描述出現了極大的差異。懷着嚴謹求學的態度經過大量資料的查閱和請教,將會在後文總結出一個完整的流程。在這裏將會就一些我自己理解存疑的地方寫出來

1、DOM樹的構建是文檔加載完成開始的?
DOM樹的構建是從接受到文檔開始的 一邊會進行將字節轉化爲字符 字符轉化爲標記 標記構建dom樹
這個過程被分爲標記化和樹構建
而這是一個漸進的過程。爲達到更好的用戶體驗,呈現引擎會力求儘快將內容顯示在屏幕上。它不必等到整個 HTML 文檔解析完畢之後,就會開始構建呈現樹和設置佈局。在不斷接收和處理來自網絡的其餘內容的同時,呈現引擎會將部分內容解析並顯示出來。

參考文檔:http://taligarsiel.com/Projects/howbrowserswork1.htm
2、渲染樹是在DOM樹和CSS樣式樹構建完畢纔開始構建的嗎?
這三個過程在實際進行的時候又不是完全獨立,而是會有交叉。會造成一邊加載,一邊解析,一邊渲染的工作現象。
參考文檔:http://www.jianshu.com/p/2d522fc2a8f8
3、css的標籤嵌套越多,越容易定位到元素
css的解析是自右至左逆向解析的,嵌套越多越增加瀏覽器的工作量,而不會越快。
因爲如果正向解析,例如「div div p em」,我們首先就要檢查當前元素到 html 的整條路徑,找到最上層的 div,再往下找,如果遇到不匹配就必須回到最上層那個 div,往下再去匹配選擇器中的第一個 div,回溯若干次才能確定匹配與否,效率很低。
逆向匹配則不同,如果當前的 DOM 元素是 div,而不是 selector 最後的 em,那隻要一步就能排除。只有在匹配時,纔會不斷向上找父節點進行驗證。
打個比方 p span.showing
你認爲從一個p元素下面找到所有的span元素並判斷是否有class showing快,還是找到所有的span元素判斷是否有class showing並且包括一個p父元素快

參考文檔:http://www.imooc.com/code/4570


二、頁面渲染的完整流程

當瀏覽器拿到HTTP報文時呈現引擎將開始解析 HTML 文檔,並將各標記逐個轉化成“內容樹”上的 DOM 節點。同時也會解析外部 CSS 文件以及樣式元素中的樣式數據。HTML 中這些帶有視覺指令的樣式信息將用於創建另一個樹結構:呈現樹。瀏覽器將根據呈現樹進行佈局繪製。

以上就是頁面渲染的大致流程。那麼瀏覽器從用戶輸入網址之後到底做了什麼呢?以下將會進行一個完整的梳理。鑑於本文是前端向的所以梳理內容會有所偏重。而從輸入到呈現可以分爲兩個部分:網絡通信頁面渲染
我們首先來看網絡通信部分:

1、用戶輸入url並敲擊回車。
**2、進行DNS解析。**如果用戶輸入的是ip地址則直接進入第三條。但去記錄毫無規律且冗長的ip地址顯然不是易事,所以通常都是輸入的域名,此時就會進行dns解析。所謂DNS(Domain Name System)指域名系統。因特網上作爲域名和IP地址相互映射的一個分佈式數據庫,能夠使用戶更方便的訪問互聯網,而不用去記住能夠被機器直接讀取的IP數串。通過主機名,最終得到該主機名對應的IP地址的過程叫做域名解析(或主機名解析)。這個過程如下所示:

瀏覽器會首先搜索瀏覽器自身的DNS緩存(緩存時間比較短,大概只有2分鐘左右,且只能容納1000條緩存)。

  • 如果瀏覽器自身緩存找不到則會查看系統的DNS緩存,如果找到且沒有過期則停止搜索解析到此結束.
  • 而如果本機沒有找到DNS緩存,則瀏覽器會發起一個DNS的系統調用,就會向本地配置的首選DNS服務器發起域名解析請求(通過的是UDP協議向DNS的53端口發起請求,這個請求是遞歸的請求,也就是運營商的DNS服務器必須得提供給我們該域名的IP地址),運營商的DNS服務器首先查找自身的緩存,找到對應的條目,且沒有過期,則解析成功。
  • 如果沒有找到對應的條目,則有運營商的DNS代我們的瀏覽器發起迭代DNS解析請求,它首先是會找根域的DNS的IP地址(這個DNS服務器都內置13臺根域的DNS的IP地址),找打根域的DNS地址,就會向其發起請求(請問www.xxxx.com這個域名的IP地址是多少啊?)
  • 根域發現這是一個頂級域com域的一個域名,於是就告訴運營商的DNS我不知道這個域名的IP地址,但是我知道com域的IP地址,你去找它去,於是運營商的DNS就得到了com域的IP地址,又向com域的IP地址發起了請求(請問www.xxxx.com這個域名的IP地址是多少?),com域這臺服務器告訴運營商的DNS我不知道www.xxxx.com這個域名的IP地址,但是我知道xxxx.com這個域的DNS地址,你去找它去,於是運營商的DNS又向linux178.com這個域名的DNS地址(這個一般就是由域名註冊商提供的,像萬網,新網等)發起請求(請問www.xxxx.com這個域名的IP地址是多少?),這個時候xxxx.com域的DNS服務器一查,誒,果真在我這裏,於是就把找到的結果發送給運營商的DNS服務器,這個時候運營商的DNS服務器就拿到了www.xxxx.com這個域名對應的IP地址,並返回給Windows系統內核,內核又把結果返回給瀏覽器,終於瀏覽器拿到了www.xxxx.com對應的IP地址,這次dns解析圓滿成功。

3、發起TCP的3次握手
拿到域名對應的IP地址之後,User-Agent(一般是指瀏覽器)會以一個隨機端口(1024< 端口 < 65535)向服務器的WEB程序(常用的有httpd,nginx等)80端口發起TCP的連接請求。這個連接請求(原始的http請求經過TCP/IP4層模型的層層封包)到達服務器端後(這中間通過各種路由設備,局域網內除外),進入到網卡,然後是進入到內核的TCP/IP協議棧(用於識別該連接請求,解封包,一層一層的剝開),還有可能要經過Netfilter防火牆(屬於內核的模塊)的過濾,最終到達WEB程序,最終建立了TCP/IP的連接。

4、建立TCP連接後發起http請求

5、服務器端響應http請求,瀏覽器得到html代碼。以下爲響應報文格式:
這裏寫圖片描述

以上是網絡通信部分,接下來將會對頁面渲染部分進行敘述。當瀏覽器拿到html後是如何進行頁面渲染的
  • 當瀏覽器拿到HTML文檔時首先會進行HTML文檔解析,構建DOM樹。

  • 遇到css樣式如link標籤或者style標籤時開始解析css,構建樣式樹。HTML解析構建和CSS的解析是相互獨立的並不會造成衝突,因此我們通常將css樣式放在head中,讓瀏覽器儘早解析css。

  • 當html的解析遇到script標籤會怎樣呢?答案是停止DOM樹的解析開始下載js。**因爲js是會阻塞html解析的,是阻塞資源。其原因在於js可能會改變html現有結構。**例如有的節點是用js動態構建的,在這種情況下就會停止dom樹的構建開始下載解析js。腳本在文檔的何處插入,就在何處執行。當 HTML 解析器遇到一個 script 標記時,它會暫停構建 DOM,將控制權移交給 JavaScript 引擎;等 JavaScript 引擎運行完畢,瀏覽器會從中斷的地方恢復 DOM 構建。而因此就會推遲頁面首繪的時間。**可以在首繪不需要js的情況下用async和defer實現異步加載。這樣js就不會阻塞html的解析了。**當HTML解析完成後,瀏覽器會將文檔標註爲交互狀態,並開始解析那些處於“deferred”模式的腳本,也就是那些應在文檔解析完成後才執行的腳本。然後,文檔狀態將設置爲“完成”,一個“加載”事件將隨之觸發。注意,異步執行是指下載。執行js時仍然會阻塞。

  • 在得到DOM樹和樣式樹後就可以進行渲染樹的構建了。應注意的是渲染樹和 DOM 元素相對應的,但並非一一對應。比如非可視化的 DOM 元素不會插入呈現樹中,例如“head”元素。如果元素的 display 屬性值爲“none”,那麼也不會顯示在呈現樹中(但是 visibility 屬性值爲“hidden”的元素仍會顯示)
    這裏寫圖片描述

  • 渲染樹構建完畢後將會進行佈局。佈局使用流模型的Layout算法。所謂流模型,即是指Layout的過程只需進行一遍即可完成,後出現在流中的元素不會影響前出現在流中的元素,Layout過程只需從左至右從上至下一遍完成即可。但實際實現中,流模型會有例外。Layout是一個遞歸的過程,每個節點都負責自己及其子節點的Layout。Layout結果是相對父節點的座標和尺寸。其過程可以簡述爲:

     父節點確定自己的寬度
     父節點完成子節點放置,確定其相對座標
     節點確定自己的寬度和高度
     父節點根據所有的子節點高度計算自己的高度
    
  • 此時renderTree已經構建完畢,不過瀏覽器渲染樹引擎並不直接使用渲染樹進行繪製,爲了方便處理定位(裁剪),溢出滾動(頁內滾動),CSS轉換/不透明/動畫/濾鏡,蒙版或反射,Z (Z排序)等,瀏覽器需要生成另外一棵樹 - 層樹。因此繪製過程如下:
    獲取 DOM 並將其分割爲多個層(RenderLayer)
    將每個層柵格化,並獨立的繪製進位圖中
    將這些位圖作爲紋理上傳至 GPU
    複合多個層來生成最終的屏幕圖像(終極layer)。


三、HTML及CSS樣式的解析

HTML解析是一個將字節轉化爲字符,字符解析爲標記,標記生成節點,節點構建樹的過程。。CSS樣式的解析則由於複雜的樣式層疊而變得複雜。對此不同的渲染引擎在處理上有所差異,後文將會就這點進行詳細講解

1、HTML的解析分爲標記化樹構建兩個階段
標記化算法
是詞法分析過程,將輸入內容解析成多個標記。HTML標記包括起始標記、結束標記、屬性名稱和屬性值。標記生成器識別標記,傳遞給樹構造器,然後接受下一個字符以識別下一個標記;如此反覆直到輸入的結束。
該算法的輸出結果是 HTML 標記。該算法使用狀態機來表示。每一個狀態接收來自輸入信息流的一個或多個字符,並根據這些字符更新下一個狀態。當前的標記化狀態和樹結構狀態會影響進入下一狀態的決定。這意味着,即使接收的字符相同,對於下一個正確的狀態也會產生不同的結果,具體取決於當前的狀態。
樹構建算法
在樹構建階段,以 Document 爲根節點的 DOM 樹也會不斷進行修改,向其中添加各種元素。
標記生成器發送的每個節點都會由樹構建器進行處理。規範中定義了每個標記所對應的 DOM 元素,這些元素會在接收到相應的標記時創建。這些元素不僅會添加到 DOM 樹中,還會添加到開放元素的堆棧中。此堆棧用於糾正嵌套錯誤和處理未關閉的標記。其算法也可以用狀態機來描述。這些狀態稱爲“插入模式”。
以下將會舉一個例子來分析這兩個階段:

<html>
  <body>
    Hello world
  </body>
</html>

標記化:初始狀態是數據狀態。遇到字符 < 時,狀態更改爲“標記打開狀態”。接收一個 a-z字符會創建“起始標記”,狀態更改爲“標記名稱狀態”。這個狀態會一直保持到接收> 字符。在此期間接收的每個字符都會附加到新的標記名稱上。在本例中,我們創建的標記是 html 標記。們會再次保持這個狀態,直到接收 >。然後將發送新的標記,並回到“數據狀態”。</html> 輸入也會進行同樣的處理。

還是以上的例子,我們來看看樹構建

**樹構建:**樹構建階段的輸入是一個來自標記化階段的標記序列。第一個模式是“initial mode”。接收 HTML 標記後轉爲“before html”模式,並在這個模式下重新處理此標記。這樣會創建一個 HTMLHtmlElement 元素,並將其附加到 Document 根對象上。
  然後狀態將改爲“before head”。此時我們接收“body”標記。即使我們的示例中沒有“head”標記,系統也會隱式創建一個 HTMLHeadElement,並將其添加到樹中。
  現在我們進入了“in head”模式,然後轉入“after head”模式。系統對 body 標記進行重新處理,創建並插入 HTMLBodyElement,同時模式轉變爲“body”。
  現在,接收由“Hello world”字符串生成的一系列字符標記。接收第一個字符時會創建並插入“Text”節點,而其他字符也將附加到該節點。
  接收 body 結束標記會觸發“after body”模式。現在我們將接收 HTML 結束標記,然後進入“after after body”模式。接收到文件結束標記後,解析過程就此結束。
解析結束後的操作
在此階段,瀏覽器會將文檔標註爲交互狀態,並開始解析那些處於“deferred”模式的腳本,也就是那些應在文檔解析完成後才執行的腳本。然後,文檔狀態將設置爲“完成”,一個“加載”事件將隨之觸發。

完整解析過程如下圖:
這裏寫圖片描述

2、CSS的解析與層疊規則

 每一個呈現器都代表了一個矩形的區域,通常對應於相關節點的 CSS 框,這一點在 CSS2 規範中有所描述。它包含諸如寬度、高度和位置等幾何信息。就是我們 CSS 裏常提到的盒子模型。構建呈現樹時,需要計算每一個呈現對象的可視化屬性。這是通過計算每個元素的樣式屬性來完成的。由於應用規則涉及到相當複雜的層疊規則,所以給樣式樹的構建造成了巨大的困難。爲什麼說它複雜?因爲同一個元素可能涉及多條樣式,就需要判斷最終到底哪條樣式生效。首先我們來了解一下css的樣式層疊規則

①層疊規則:
根據不同的樣式來源優先級排列從小到大:
1>、用戶端聲明:來自瀏覽器的樣式,被稱作 UA style,是瀏覽器默認的樣式。 比如,對於 DIV 元素,瀏覽器默認其 ‘display’ 的特性值是 “block”,而 SPAN 是 “inline”。
2>、一般用戶聲明:這個樣式表是使用瀏覽器的用戶,根據自己的偏好設置的樣式表。比如,用戶希望所有 P 元素中的字體都默認顯示成藍色,可以先定義一個樣式表,存成 css 文件。
3>、一般作者聲明:即開發者在開發網頁時,所定義的樣式表。
4>、加了’!important’ 的作者聲明
5>、加了’!important’ 的用戶聲明
!important 規則1:根據 CSS2.1 規範中的描述,’!important’ 可以提高樣式的優先級,它對樣式優先級的影響是巨大的。 關於important在css2.1中的定義請點擊這裏
注意,’!important’ 規則在 IE7 以前的版本中是被支持不完善。因此,經常被用作 CSS hack2。

如果來源和重要性相同則根據CSS specificity來進行判定。
**特殊性的值可以看作是一個由四個數組成的一個組合,用 a,b,c,d 來表示它的四個位置。 依次比較 a,b,c,d 這個四個數比較其特殊性的大小。**比如,a 值相同,那麼 b 值大的組合特殊性會較大,以此類推。 注意,W3C 中並不是把它作爲一個 4 位數來看待的。
a,b,c,d 值的確定規則:

  • 如果 HTML 標籤的 ‘style’ 屬性中該樣式存在,則記 a 爲 1;
  • 數一下選擇器中 ID 選擇器的個數作爲 b 的值。比如,樣式中包含 ‘#c1’ 和 ‘#c2’ 的選擇器;
  • 其他屬性以及僞類(pseudo-classes)的總數量是 c 的值。比如’.con’,’:hover’ 等;
  • 元素名和僞元素的數量是 d 的值

在這裏我們來看一個W3C給出的例子:

*             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

那麼在如下例子中字體的顯示應當爲綠色:

<HEAD>
<STYLE type="text/css">
 #x97z { color: red }
</STYLE>
</HEAD>
<BODY>
<P ID=x97z style="color: green">
</BODY>

總結爲表格的話計算規則如下:
這裏寫圖片描述
②CSS解析

爲了簡化樣式計算,Firefox 還採用了另外兩種樹:規則樹和樣式上下文樹。Webkit 也有樣式對象,但它們不是保存在類似樣式上下文樹這樣的樹結構中,只是由 DOM 節點指向此類對象的相關樣式。

1>、Firefox的規則樹和樣式上下文樹:
樣式上下文包含端值。要計算出這些值,應按照正確順序應用所有的匹配規則,並將其從邏輯值轉化爲具體的值。例如,如果邏輯值是屏幕大小的百分比,則需要換算成絕對的單位。規則樹的點子真的很巧妙,它使得節點之間可以共享這些值,以避免重複計算,還可以節約空間。
所有匹配的規則都存儲在樹中。路徑中的底層節點擁有較高的優先級。規則樹包含了所有已知規則匹配的路徑。規則的存儲是延遲進行的。規則樹不會在開始的時候就爲所有的節點進行計算,而是隻有當某個節點樣式需要進行計算時,纔會向規則樹添加計算的路徑。
這個想法相當於將規則樹路徑視爲詞典中的單詞。如果我們已經計算出如下的規則樹:
這裏寫圖片描述
假設我們需要爲內容樹中的另一個元素匹配規則,並且找到匹配路徑是 B - E - I(按照此順序)。由於我們在樹中已經計算出了路徑 A - B - E - I - L,因此就已經有了此路徑,這就減少了現在所需的工作量。

那麼Firefox是如何解決樣式計算難題的呢?接下來看一個樣例,假設我們有如下HTML代碼:

<html>
  <body>
     <div class="err" id="div1">
        <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
        </p>
     </div>
     <div class="err" id="div2">another error</div>
  </body>
</html>

並且我們有如下規則:

div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

爲了簡便起見,我們只需要填充兩個結構:color 結構和 margin 結構。color 結構只包含一個成員(即“color”),而 margin 結構包含四條邊。
形成的規則樹如下圖所示(節點的標記方式爲“節點名 : 指向的規則序號”):

這裏寫圖片描述

上下文樹如下圖所示(節點名 : 指向的規則節點):

這裏寫圖片描述

假設我們解析 HTML 時遇到了第二個 <div> 標記,我們需要爲此節點創建樣式上下文,並填充其樣式結構。
經過規則匹配,我們發現該 <div> 的匹配規則是第 1、2 和 6 條。這意味着規則樹中已有一條路徑可供我們的元素使用,我們只需要再爲其添加一個節點以匹配第 6 條規則(規則樹中的 F 節點)。
我們將創建樣式上下文並將其放入上下文樹中。新的樣式上下文將指向規則樹中的 F 節點。
現在我們需要填充樣式結構。首先要填充的是 margin 結構。由於最後的規則節點 (F) 並沒有添加到 margin 結構,我們需要上溯規則樹,直至找到在先前節點插入中計算過的緩存結構,然後使用該結構。我們會在指定 margin 規則的最上層節點(即 B 節點)上找到該結構。
我們已經有了 color 結構的定義,因此不能使用緩存的結構。由於 color 有一個屬性,我們無需上溯規則樹以填充其他屬性。我們將計算端值(將字符串轉化爲 RGB 等)並在此節點上緩存經過計算的結構。
第二個 元素處理起來更加簡單。我們將匹配規則,最終發現它和之前的 span 一樣指向規則 G。由於我們找到了指向同一節點的同級,就可以共享整個樣式上下文了,只需指向之前 span 的上下文即可。
對於包含了繼承自父代的規則的結構,緩存是在上下文樹中進行的(事實上 color 屬性是繼承的,但是 Firefox 將其視爲 reset 屬性,並緩存到規則樹上)。
例如,如果我們在某個段落中添加 font 規則:

p {font-family:Verdana;font size:10px;font-weight:bold}

那麼,該段落元素作爲上下文樹中的 div 的子代,就會共享與其父代相同的 font 結構(前提是該段落沒有指定 font 規則)。

2>、Webkit的樣式解析
在 Webkit 中沒有規則樹,因此會對匹配的聲明遍歷 4 次。首先應用非重要高優先級的屬性(由於作爲其他屬性的依據而應首先應用的屬性,例如 display),接着是高優先級重要規則,然後是普通優先級非重要規則,最後是普通優先級重要規則。這意味着多次出現的屬性會根據正確的層疊順序進行解析。最後出現的最終生效。


四、渲染樹的構建

樣式樹和DOM樹連接在一起形成一個渲染樹,渲染樹用來計算可見元素的佈局並且作爲將像素渲染到屏幕上的過程的輸入。值得一提的是,Gecko 將視覺格式化元素組成的樹稱爲“框架樹”。每個元素都是一個框架。Webkit 使用的術語是“渲染樹”,它由“呈現對象”組成。 Webkit 和 Gecko 使用的術語略有不同,但整體流程是基本相同的。

接下來將來看一下兩種渲染引擎的工作流程:
Webkit 主流程:
這裏寫圖片描述

Mozilla 的 Gecko 呈現引擎主流程
這裏寫圖片描述

雖然 Webkit 和 Gecko 使用的術語略有不同,但整體流程是基本相同的。

Gecko 將視覺格式化元素組成的樹稱爲“框架樹”。每個元素都是一個框架。
Webkit 使用的術語是“呈現樹”,它由“呈現對象”組成。
對於元素的放置,Webkit 使用的術語是“佈局”,而 Gecko 稱之爲“重排”。
對於連接 DOM 節點和可視化信息從而創建呈現樹的過程,Webkit 使用的術語是“附加”。有一個細微的非語義差別,就是 Gecko 在 HTML 與 DOM 樹之間還有一個稱爲“內容槽”的層,用於生成 DOM 元素。我們會逐一論述流程中的每一部分。


五、關於瀏覽器渲染過程中需要了解的概念
Repaint(重繪)——屏幕的一部分要重畫,比如某個CSS的背景色變了。但是元素的幾何尺寸沒有變。
Reflow(重排)——意味着元件的幾何尺寸變了,我們需要重新驗證並計算Render Tree。是Render Tree的一部分或全部發生了變化。這就是Reflow,或是Layout。reflow 會從這個root frame開始遞歸往下,依次計算所有的結點幾何尺寸和位置,在reflow過程中,可能會增加一些frame,比如一個文本字符串必需被包裝起來。
onload事件——當 onload 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片,flash都已經加載完成了。
DOMContentLoaded 事件——當 DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片,flash。
首屏時間——當瀏覽器顯示第一屏頁面所消耗的時間,在國內的網絡條件下,通常一個網站,如果“首屏時間”在2秒以內是比較優秀的,5秒以內用戶可以接受,10秒以上就不可容忍了。
白屏時間
白屏時間——指瀏覽器開始顯示內容的時間。但是在傳統的採集方式裏,是在HTML的頭部標籤結尾裏記錄時間戳,來計算白屏時間。在這個時刻,瀏覽器開始解析身體標籤內的內容。而現代瀏覽器不會等待CSS樹(所有CSS文件下載和解析完成)和DOM樹(整個身體標籤解析完成)構建完成纔開始繪製,而是馬上開始顯示中間結果。所以經常在低網速的環境中,觀察到頁面由上至下緩慢顯示完,或者先顯示文本內容後再重繪成帶有格式的頁面內容。


六、頁面優化方案

本文的主題在於從瀏覽器的渲染過程談頁面優化。瞭解瀏覽器如何進行解析,我們可以在構建DOM結構,組織css選擇器時,選擇最優的寫法,提高瀏覽器的解析速率。理解瀏覽器如何進行渲染,明白渲染的過程,我們在設置元素屬性,編寫js文件時,可以減少”重繪“”重新佈局“的消耗。接下來將總結根據以上只是提出的頁面優化方案

**1、減少資源請求的次數和壓縮數據內容。**因爲資源的請求是一個複雜的過程。網速相同的條件下,下載一個100KB的圖片比下載兩個50KB的圖片要快。所以,請減少HTTP請求。
①進行資源打包,將需要多次請求的資源進行打包減少資源請求次數,如webpack等。
②使用雪碧圖,可以避免因不同圖片引起的多次資源下載

**2、高效合理的css選擇符可以減輕瀏覽器的解析負擔。**因爲css是逆向解析的所以應當避免多層嵌套。
. 避免使用通配規則。如 *{} 計算次數驚人!只對需要用到的元素進行選擇
. 儘量少的去對標籤進行選擇,而是用class。如:#nav li{},可以爲li加上nav_item的類名,如下選擇.nav_item{}
. 不要去用標籤限定ID或者類選擇符。如:ul#nav,應該簡化爲#nav
. 儘量少的去使用後代選擇器,降低選擇器的權重值。後代選擇器的開銷是最高的,儘量將選擇器的深度降到最低,最高不要超過三層,更多的使用類來關聯每一個標籤元素。
. 考慮繼承。瞭解哪些屬性是可以通過繼承而來的,然後避免對這些屬性重複指定規則

3、從js層面談頁面優化
①解決渲染阻塞
如果在解析HTML標記時,瀏覽器遇到了JavaScript,解析會停止。只有在該腳本執行完畢後,HTML渲染纔會繼續進行。所以這阻塞了頁面的渲染。
解決方法:在標籤中使用 async或defer特性
②減少對DOM的操作
對DOM操作的代價是高昂的,這在網頁應用中的通常是一個性能瓶頸。
解決辦法:修改和訪問DOM元素會造成頁面的Repaint和Reflow,循環對DOM操作更是罪惡的行爲。所以請合理的使用JavaScript變量儲存內容,考慮大量DOM元素中循環的性能開銷,在循環結束時一次性寫入。
減少對DOM元素的查詢和修改,查詢時可將其賦值給局部變量。
③使用JSON格式來進行數據交換
JSON是一種輕量級的數據交換格式,採用完全獨立於語言的文本格式,是理想的數據交換格式。同時,JSON是 JavaScript原生格式,這意味着在 JavaScript 中處理 JSON數據不需要任何特殊的 API 或工具包。
④讓需要經常改動的節點脫離文檔流
因爲重繪有時確實不可避免,所以只能儘可能限制重繪的影響範圍。

4、.使用CDN加速(內容分發網絡)
其基本思路是儘可能避開互聯網上有可能影響數據傳輸速度和穩定性的瓶頸和環節,使內容傳輸的更快、更穩定。通過在網絡各處放置節點服務器所構成的在現有的互聯網基礎之上的一層智能虛擬網絡,CDN系統能夠實時地根據網絡流量和各節點的連接、負載狀況以及到用戶的距離和響應時間等綜合信息將用戶的請求重新導向離用戶最近的服務節點上。

5、 精簡CSS和JS文件
如使用 YUI Compressor,它的特點是:移除註釋;移除額外的空格;細微優化;標識符替換。

  相信以上優化方案之所以行之有效的原因大都可以在本文中找出原因。理論是用來指導實踐的,即不能閉門造車式的埋頭苦幹,也不能毫不實踐的誇誇其談。這樣纔會形成完整的知識體系,讓知識體系樹更加龐大。第一次進行一個完整的梳理,覺得對自己的理解提升也是很高的,希望自己以後可以將所掌握的知識運用在體系當中,寫出更加精簡高效的代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章