天天看點

從web浏覽器的渲染到性能優化

本文主要講解web浏覽器的渲染原理、流程到性能優化。主要有以下幾點:

(1) script标簽中的屬性defer和async的差別

(2) 浏覽器的渲染順序

(3) 如何防止阻塞DOM渲染

(4) 如何保證首屏優化、關鍵渲染路徑優化

(5) 如何從浏覽器渲染、網絡請求、js引擎機制優化性能

一: script标簽中的屬性defer和async的差別

要想讓script标簽中的defer和async屬性生效,必須引入屬性src,即js檔案必須是外部引入的腳本檔案。

下面讨論兩個基本點:

 (1) 下載下傳時是否會阻塞DOM的渲染

 (2) 執行時是否會阻塞DOM的渲染

首先談一下async屬性的特性:

(1) async屬性是賦予腳本異步屬性

(2) 頁面一加載的時候,就立即下載下傳腳本,不妨礙頁面中的其他操作,比如下載下傳其他資源、等待加載其他腳本

(3) js一旦下載下傳好,就會立即執行

(4) 和文檔同時呈現,标記為async的js腳本不會按照順序執行

(5) 一般js不需要改變頁面的DOM的時候,可以使用屬性async進行異步加載

(6) 注意,異步腳本不要在加載期間修改DOM。有async屬性的腳本,下載下傳的時候不會阻塞DOM的解析,但是執行的時候會阻塞頁面的解析

(7) 具有async屬性的js腳本一定會在頁面的load事件前執行,,但可能會在DOMContentLoaded事件觸發之前或之後執行

其次談一下defer屬性的特性:

(1) defer屬性賦予腳本延遲屬性

(2)頁面一加載的時候,就立即下載下傳具有defer屬性的js腳本,下載下傳期間不阻塞DOM的解析,但等到整個DOM都解析完成之後才運作具有defer屬性的JS腳本

(3) 下載下傳的時候不阻塞DOM解析,執行具有defer屬性的js時候也不會阻塞DOM的解析

(4) 具有defer屬性的JS腳本會按照書寫的前後順序執行,是以腳本具有前後依賴關系可以放心使用(在實際中具有defer屬性的腳本并不一定會按照書寫順序執行,是以最好是隻讓一個js腳本具有defer屬性)

(5) 具有defer屬性的js腳本會先于DOMContentLoaded事件執行(實際當中,也不一定會在DOMContentLoaded事件前執行)

現在總結一下async和defer屬性的共同點:

(1) 兩者在下載下傳的時候都不會阻塞DOM解析

(2) 兩者都隻對外部腳本有效,如<script  src='index.js' defer='defer' ></script>或者<script  src='index.js'  async='async' ></script>

(3) 都可以使用onload事件進行一系列處理

現在總結一下async和defer屬性的不同點:

(1) 不具有async和defer屬性的js腳本,浏覽器根據其所在位置阻塞解析,被下載下傳緊接着執行,直到完成。

(2) 具有async屬性的js腳本,在下載下傳的時候不會阻塞DOM的解析。但是js檔案一旦下載下傳後之後,就會立即執行腳本,此時有可能會阻塞DOM的解析。即具有async屬性的js腳本下載下傳時候不會阻塞DOM的解析,但在執行的時候有可能會阻塞DOM的解析。

(3) 具有defer屬性的js腳本,在下載下傳的時候不會阻塞DOM的解析。js加載完之後,等待DOM渲染完成之後在執行js腳本,此時不會阻塞DOM的解析。即具有async屬性的js腳本下載下傳和執行的時候都不會阻塞DOM的解析。

兩者的應用場景

(1) 具有async屬性的js腳本,适合基本沒有DOM操作、和子產品的加載順序無關、執行時間要短,否則對首屏還是有很大的影響的。

(2) 具有defer屬性的js腳本,按序加載,加載和執行的時候都不會阻塞DOM的解析。

二:浏覽器關鍵渲染順序

接下來讓我們來思考幾個問題:

(1) 具有async屬性的js腳本可能會阻塞DOM的解析,那麼css在加載的時候回阻塞DOM的解析嗎?

(2) 其他資源如何下載下傳?

(3) 其他資源會阻塞DOM的解析嗎?

(4)DOM parse是什麼?

(5) 事件DOMContentLoaded和load分别是基于那個節點觸發的?

首先讓我們來看一下浏覽器的渲染過程

下圖展示了整個浏覽器的渲染過程

從web浏覽器的渲染到性能優化

(1) 文檔對象模型(DOM)

從web浏覽器的渲染到性能優化

上圖展示了在浏覽器裡面,html的位元組碼被轉換成DOM的過程

A、Bytes->Characters轉換:根據位元組Bytes的編碼規則,将其轉換為特定的字元Characters

B、Characters->Tokens(生成Tokens):将Characters轉化為w3c定義的各種特定标簽,生成Tokens(令牌)

C、Tokens->Nodes(詞法解析):比對字元串,将Tokens按照規則轉換成節點對象(Nodes),其具有屬性和規則

D、Nodes->DOM(DOM建構):根據每個節點的層次關系和屬性,轉換為直覺的樹形結構,具有明确的父子關系

至此,得到頁面完整的DOM模型,以後的頁面渲染(Render Tree)包括布局(Layout)和繪制(Paint)都是基于DOM的

注意:HTML都是增量建構的,在HTML檔案還在傳輸的時候,HTML parse就已經開始了。

DOM代表頁面的結構,決定整個初始化頁面的布局;CSSOM決定頁面的樣式

(2) CSS對象模型(CSSOM)

從web浏覽器的渲染到性能優化

    如上圖所示是CSSOM建構流程圖,将css檔案的位元組碼轉換為符合浏覽器特定規則的字元,然後浏覽器對其解析和構成樹。整個計算的過程包括一套複雜的特異度計算規則(css屬性來源->特異度大小->書寫順序前後覆寫),最終确定每個節點的樣式值,形成CSSOM。 css被認為 是一種渲染阻塞資源(所謂的CSS白屏),因為渲染樹是依賴CSSOM才能生成,然後才到浏覽器的布局渲染流程,是以才将css放到head标簽裡面。

(3) 渲染樹(Render Tree)

渲染樹生成的大概過程如下:

A、從DOM的根節點開始周遊每個在HTML和CSS上的可見節點

B、對每個可見節點,為其找到适配的CSSOM并且組合他們

C、将每個節點(包括内容和樣式)組建成Render Tree

所謂的可見節點:渲染樹包含了渲染網頁所需的所有節點,不需要渲染的節點是不會合并到渲染樹裡面的,比如中繼資料元素meta,base等,設定display:none的節點

(4) 布局(Layout)-計算渲染樹節點大小

布局的最終效果是形成一個“盒子模型”,他需要精确地計算出每個元素所占據的位置坐标,将相對測量值(rem,vw,vh,em)轉換成絕對像素。

下面講解一下相對測量值的轉換規則:

A、rem是相對根元素<html>的font-size值确定大小的;

B、vw,vh是相對視視窗的大小來确定的

我們可以在js裡面改變節點的樣式,但是css元素的位置和大小改變的,進而改變整體布局的話,那麼浏覽器會重新布局和渲染,這在開發過程中要注意避免和減小性能損耗的。

(5) 繪制(Paint)

根據background,border,box-shadow等樣式和HTML内容,将Layout生成的區域填充為最終顯示在螢幕上的像素

注意:DOMContentLoaded發生在DOM樹建構完成之後發生的,也就是DOM解析完</html>的那一刻。load事件則是在所有資源都加載渲染之後才會觸發

三、優化關鍵渲染路徑(在很大程度上是指了解和優化HTML   CSS  JS 之間的依賴關系)

優化關鍵渲染路徑是指優先顯示與使用者目前操作有關的内容。

1、CSS阻塞渲染

      CSSOM形成前,浏覽器不會渲染任何已經處理過的内容,是以css被視為阻塞渲染的資源(主要指chrom浏覽器,各大浏覽器的實作有差異)

      要解決css阻塞的問題有幾個次元:

       A、網速;         

       B、大小;

       C、盡早并行下載下傳;

       D、盡早開始建構CSSOM;

       E、建構CSSOM的速度

       css阻塞渲染的優化方法:

       1、媒體查詢

             前端的使用場景衆多,需要寫适配的多端的代碼,媒體查詢(media)雖然下載下傳全部的代碼,但是隻解析符合媒體查詢的條件的代碼,這樣就做到盡量少的阻塞渲染

        2、preload(盡早并行下載下傳)     

<link rel="preload" href="index.css" target="_blank" rel="external nofollow"  as='style' οnlοad="this.rel='stylesheet'">
           

             将rel設定成preload,相當于加了一個标志位,浏覽器解析的時候會提前建立連接配接(或加載資源),做到盡早并行下載下傳,然後再onload事件響應之後将link的rel屬性改成               stylesheet即可進行解析。

        3、動态添加link       

var style = document.createElement('link');
        style.rel = 'stylesheet';
        style.href = 'index.css';
        document.head.appendChild(style);
           

               js動态添加DOM元素的link,不會阻塞渲染

         4、代碼簡練、不使用css計算、避免使用統配、進階選擇器

        使用css的時候需要注意以下幾點:

        1、将css放在head标簽裡面:不管内聯還是外聯都盡早開始下載下傳或者建構CSSOM(前提是這個css是首屏必須的)

         2、避免使用css  import:在css中可以使用import引入另一個樣式表,不過這會在建構CSSOM時增加一次網絡來回時間

         3、适度内聯css,衡量其他因素,如外聯網絡來回影響多大、HTML大小、CSS大小

         4、全面考慮渲染情況:網速差、檔案下載下傳失敗等,防止白屏時間過長

         5、讨論css在 IE   Chrom    Firefox的差別

                A、IE隻要一遇到<html>标簽就開始繪制

                B、Chrom不管css放在前面還是後面,都要等到CSSOM建構形成之後才會繪制在頁面上

                C、Firefox:css放在head裡面則會阻塞繪制,放在body的末尾則會先繪制前面的标簽

2、js阻塞渲染

     一般情況下,<script>标簽的最佳實踐是放在</body>的前面。

      js能操作DOM   CSSOM,為了減少不必要的沖突和低效,浏覽器都會做最壞的打算,是以正常情況下js的執行會阻塞DOM建構,等待CSSOM的建構完畢在執行。

      A、在HTML解析器解析到script标簽的時候,會停止DOM建構,将控制權轉交給js引擎,當js執行完畢的時候,浏覽器會繼續DOM建構

       B、js可能會操作CSSOM,是以浏覽器未先将CSSOM建構完畢,那麼js就會暫停執行,同時DOM建構也會暫停

       以上說法可以總結為一下幾點:

      1、腳本在文檔中的位置相當重要,因為其根html    css有很強的依賴關系

      2、在HTML解析器解析到script标簽後,會停止DOM建構

      3、js可以操作DOM         CSSOM,但進行這些行為的時候要確定相應的DOM和CSSOM已經存在

      4、js執行将暫停,直至CSSOM就緒

      總的來說,js在DOM  CSSOM之間存在大量的依賴關系,根本目的是為了有序的高效的渲染頁面,要達到這一目的,則需要明确依賴關系。

      優化js 渲染阻塞的方法:(處理依賴關系的方法)

      1、js放在</body>的前面

      2、defer

      3、async

      4、避免長時間的運作js,若初始化必須則考慮适當的分割

3、font阻塞渲染

      浏覽器為了避免FOUT(flash of unstyled text),會盡量等待字型加載完成後,在顯示應用該字型的内容。

      隻有當字型超過一段時間仍未加載成功時,浏覽器才會降級使用系統字型,每個浏覽器都規定了逾時的時間

      但存在内容無法盡快的被展示,導緻空白。

       可以參考異步加載字型庫

4、關鍵資源路徑

      三大名額:

     A、關鍵資源大小:優化請求時間、解析時間、渲染時間

     B、網絡請求來回數目-優化氫氣時間

     C、關鍵資源數目:優化解析時間、渲染時間

     減少資源大小:

     A、避免傳回無用的内容

     B、針對特定語言的源碼壓縮

     C、通用文本壓縮

     D、圖檔壓縮

     減少請求來回時間

     A、伺服器優化

           1、chunked encoding

            2、盡早傳回資料

            3、服務端渲染

      B、合理利用緩存 

            1、cache control

            2、ETag

            3、localstorage

            4、service  worker

        C、優化網絡

            1、HTTP2

            2、CDN

            3、域名分割

            4、減少重定向 

            5、resoure-hint

下面總結一下優化關鍵渲染路徑的一般步驟

      1、分析關鍵渲染路徑中的資源的大小、來回、渲染順序

       2、最大限度删減關鍵資源數目,也就是盡量隻渲染首屏必須的資源,其他的異步或延遲(async    defer)

       3、合并請求數目,減少請求往返次數,減少資源位元組數(内聯js、css)

       4、優化加載渲染順序,最大化利用浏覽器渲染引擎和js引擎(調整資源DOM順序)

四、性能優化

       1、代碼層面:避免使用css表達式、避免使用進階選擇器、通配選擇器等

        2、緩存利用:緩存ajax,使用CDN,使用外部的js   css檔案以便緩存,添加Espires頭,服務端配置Etag,減少DNS查找

        3、請求數量:合并樣式和腳本,使用css圖檔精靈,初始首屏之外的圖檔資源按需加載,靜态資源延遲加載

        4、請求寬帶:壓縮檔案,開啟GZI

        1、代碼層面的優化:

              A、應hash-table來優化查找

               B、少用全局變量

              C、用innerHTML代替DOM操作,減少DOM操作次數,優化js性能

              D、用setTimeout來避免頁面失去響應

              E、緩存DOM節點查找的結果 

              F、避免使用css表達式

              G、避免全局查詢

              H、避免使用with(with會自己建立自己的作用域,會增加作用域長度)

               I、多個變量聲明合并

               J、避免圖檔和iframe等的空src。空src會重新加載目前頁面,影響速度和效率

              K、盡量避免在HTML标簽中的寫style屬性

          2、移動端性能優化

               A、盡量使用css3動畫,開啟硬體加速

               B、适當使用touch事件代替click事件

               C、避免使用css3的漸變和陰影效果

               D、可以使用transform:translateZ(0)來開啟硬體加速

               E、不濫用float。float在渲染時的計算量比較大,盡量減少使用

               F、不濫用web字型,web字型需要下載下傳,解析,重繪目前頁面,盡量減少使用

               G、合理使用requestAnimationFrame動畫 代替setTimeout

               H、css中的屬性(css3 transition、css3 3D transforms、opacity、canvas、webGL、video )會觸發GPU渲染,請合理使用。過度使用會引發手機耗電增加

        Etag的定義

                 當發送一個伺服器請求的時候,浏覽器首先會進行緩存過期判斷,浏覽器根據緩存過期時間判斷緩存檔案是否過期

                場景一、如果沒有過期,則不向伺服器發送請求,直接使用緩存中的内容。此時在浏覽器console中可以看到200   OK(from cache),此時的情況完全使用緩存,浏覽                                 器和伺服器沒有任何互動。

                 場景二、如果已經過期,則向伺服器發送請求,此時請求中會帶上1中設定的檔案的修改時間和Etag,然後進行資源更新判斷,伺服器根據浏覽器傳過來的檔案的修                                 改時間,判斷自浏覽器上一次請求之後,檔案是否有被修改過;根據Etag,判斷檔案内容自上一次請求之後,有沒有發生變化

                  情形一、若兩種判斷的結論都是檔案沒有被修改過,則伺服器就不給浏覽器發index.html的内容,直接告訴他,檔案沒有被修改過,使用緩存--------304 not                                                 modifined,此時浏覽器就會從本地緩存中擷取index.html的内容。這種情況叫做協定緩存,浏覽器和伺服器之間有一次請求互動。

                 情形二、如果修改時間和檔案内容判斷有任意一個沒有通過,則伺服器會受理此次請求,之後的操作同1,1隻有get請求會被緩存,post請求不會

        ETag應用

                   ETag由伺服器端生成,用戶端通過if-match(if-none-match)這個條件判斷請求來驗證資源是否修改。常見的使用是if-none-match,請求一個檔案的流程可能如                         下:

                  --------第一次請求-------------

                  1、用戶端發起HTTP GET 請求一個檔案;

                   2、伺服器處理請求,傳回檔案内容和一堆header,包括ETag(例如“2e681a-6-5d044840”)(假設伺服器支援Etag生成和已經開啟Etag),狀态碼200

                  -------第二次請求-------------

                 用戶端發起HTTP GET請求一個檔案,注意這個時候用戶端同時發送一個if-none-match頭,這個頭的内容就是第一次請求時伺服器傳回的Etag:2e681a-6-5d044840

                 伺服器判斷發送過來的Etag和計算出來的Etag比對,是以if-none-match為false,不傳回200,傳回304,用戶端繼續使用本地緩存

                  ---------------------

                  如果伺服器又設定了cache-control:max-age和Expires,那如何處理呢?

                  答案是同時使用,也就是說在完全比對if-modifined-since和if-none-match,即檢查修改時間和Etag之後,伺服器才會傳回304

                 -------------

                  使用Etag的目的是:Etag主要是解決Last-Modified無法解決的一些問題。

               Expires和Cache-Control

                Expires要求用戶端和伺服器的時鐘嚴格同步。HTTP1.1引入Cache-Control來科夫Expires頭的限制,如果max-age和Expires同時出現,則max-aage有更高的優先級

轉載路徑:http://blog.csdn.net/allenliu6/article/details/76609929

繼續閱讀