為什么要了解瀏覽器加載、解析、渲染這個過程?

了解瀏覽器如何進行加載,我們可以在引用外部樣式文件,外部js時,將他們放到合適的位置,使瀏覽器以最快的速度將文件加載完畢。

了解瀏覽器如何進行解析,我們可以在構建DOM結構,組織css選擇器時,選擇最優(yōu)的寫法,提高瀏覽器的解析速率。

了解瀏覽器如何進行渲染,明白渲染的過程,我們在設置元素屬性,編寫js文件時,可以減少”重繪“”重新布局“的消耗。

這三個過程在實際進行的時候又不是完全獨立,而是會有交叉。會造成一邊加載,一邊解析,一邊渲染的工作現象。

瀏覽器是如何進行加載、解析、渲染的呢?

1.用戶訪問網頁,DNS服務器(域名解析系統(tǒng))會根據用戶提供的域名查找對應的IP地址,找到后,系統(tǒng)會向對應IP地址的網絡服務器發(fā)送一個http請求。

2.網絡服務器解析請求,并發(fā)送請求給數據庫服務器。

3.數據庫服務器將請求的資源返回給網絡服務器,網絡服務器解析數據,并生成html文件,放入http response中,返回給瀏覽器。

4.瀏覽器解析 http response。
1~4步驟需要了解HTTP協議。
訪問服務器端可能遭遇的問題:如果網絡服務器無法獲取數據庫服務器返回的資源文件(http response 404),或者由于并發(fā)原因暫時無法處理用戶的http請求(http response 500)

5.瀏覽器解析 http response后,需要下載html文件,以及html文件內包含的外部引用文件,及文件內涉及的圖片或者多媒體文件。

 

加載

即為獲取資源文件的過程,不同瀏覽器,以及他們的不同版本在實現這一過程時,會有不同的實現效果(資源間互相阻塞),。(需要學習使用timeline來做測試,我還不太會用,學會了在上文。)

<!DOCTYPE html><html>     <head>           <meta charset="utf-8">           <link rel="stylesheet"  href="test.css"  type="text/css" />           <script src="test.js" type="text/javascript"></script>     </head>     <body>    
           <p>阻塞</p>           <img src="test.jpg" /> 
     </body></html>

加載過程中遇到外部css文件,瀏覽器另外發(fā)出一個請求,來獲取css文件。
遇到圖片資源,瀏覽器也會另外發(fā)出一個請求,來獲取圖片資源。這是異步請求,并不會影響html文檔進行加載,但是當文檔加載過程中遇到js文件,html文檔會掛起渲染(加載解析渲染同步)的線程,不僅要等待文檔中js文件加載完畢,還要等待解析執(zhí)行完畢,才可以恢復html文檔的渲染線程。

原因:JS有可能會修改DOM,最為經典的document.write,這意味著,在JS執(zhí)行完成前,后續(xù)所有資源的下載可能是沒有必要的,這是js阻塞后續(xù)資源下載的根本原因。
辦法:可以將外部引用的js文件放在</body>前。

雖然css文件的加載不影響js文件的加載,但是卻影響js文件的執(zhí)行,即使js文件內只有一行代碼,也會造成阻塞。

原因:可能會有 var width = $('#id').width(),這意味著,js代碼執(zhí)行前,瀏覽器必須保證css文件已下載和解析完成。這也是css阻塞后續(xù)js的根本原因。
辦法:當js文件不需要依賴css文件時,可以將js文件放在頭部css的前面。

當然除了,<link href="" />這種形式,內部<style></style>這種樣式定義,在考慮阻塞時也要考慮。
以上對于代碼布局對文檔加載的影響是比較基礎的,隨著瀏覽器的升級,以及js技術的應用,html的發(fā)展,web性能也會逐漸提高。
列舉一下,以后可以逐一擴充一下:

  • 不要在外部調用的js文件中調用運行時間較長的函數,如果一定要用,可以使用setTimeout函數。

    為什么呢?

    1. 瀏覽器GUI渲染線程

    2. javascript引擎線程

    3. 瀏覽器定時器觸發(fā)線程(setTimeout)

    4. 瀏覽器事件觸發(fā)線程

    5. 瀏覽器http異步請求線程(.jpg <link />這類請求)
      原因:瀏覽器有以上五個常駐線程
      發(fā)現第3個線程,我們便知道,如果在js內使用setTimeout()那么會調用另一個線程。
      注意:這里也涉及到 阻塞 的現象,當js引擎線程(第二個)進行時,會掛起其他一切線程,這個時候3、4、5這三類線線程也會產生不同的異步事件(這句話不懂?。?,由于 javascript引擎線程為單線程,所以代碼都是先壓到隊列,采用先進先出的方式運行,事件處理函數,timer函數也會壓在隊列中,不斷的從隊頭取出事件,這就叫:javascript-event-loop。

  • 現代瀏覽器存在 prefetch 優(yōu)化,瀏覽器會另外開啟線程,提前下載js、css文件,需要注意的是,預加載js并不會改變dom結構,他將這個工作留給主加載。

  • 預加載網頁,利用空余時間來提前加載該網頁的后續(xù)網頁。

    <link rel="prefetch" href="http://">
  • 為js腳本添加defer屬性,使得瀏覽器不等js腳本加載執(zhí)行完,就加載后面的圖片。既然圖片資源都已經加載出來了,就不要在js內寫document.write啦。

    <script defer="true" src="JavaScript.js" type="text/javascript"/>

解析

解析的概念有些多,需要另寫一篇文章。于是我就先簡單的寫一下。

  • html文檔解析生成解析樹即dom樹,是由dom元素及屬性節(jié)點組成,樹的根是document對象。

    DOM:文檔對象模型的縮寫,是html文檔的對象表示,作為html的外部接口供js調用。

    document.getElementById('test').style.display="none";//通過dom接口將id為test的display值設為none。

  • css解析將css文件解析為樣式表對象。該對象包含css規(guī)則,該規(guī)則包含選擇器和聲明對象。


css解析.png

  • js解析因為文件在加載的同時也進行解析,詳看js加載部分。

渲染

即為構建渲染樹的過程,他是原來DOM樹的可視化表示,構建這棵樹是為了以正確的順序繪制文檔內容。
渲染樹和DOM樹的關系,不可見的dom元素(<head>…</head> display=none)不會被插入渲染樹中。還有像一些節(jié)點的位置為絕對或浮動定位(需要css知識理解),這些節(jié)點會在文本流之外,因此會在兩棵樹上的不同位置,渲染樹標識出真實的位置,并用一個占位結構標識出他們原來的位置。

渲染最大的一個困難就是為每一個dom節(jié)點計算符合他的最終樣式。
  • 為每一個元素查找到匹配的樣式規(guī)則,需要遍歷整個規(guī)則表。關于遍歷規(guī)則表的方法,我之前理解錯啦。

     #test p{ color:#999999}

    正解:遍歷是自右向左,也就是先查詢到p元素,再找到上一級id為test的元素。之前的理解正好相反。這樣發(fā)現,遍歷的效率好低。
    為什么:可以去看之前css解析時,生成的樣式對象,遍歷的順序,自然是從樹的低端向上遍歷。

計算樣式的一些困難:

  1. 樣式數據是非常大的結構,保存這樣是的數據是很耗內存的。

  2. 選擇器迭代太深,造成太多的無用遍歷。

  3. 樣式規(guī)則涉及非常復雜的級聯,定義了規(guī)則的層次(理解:<head>里引用的外部樣式表,會被局部樣式表中同一屬性的設置取代。還有例如body內對font的設置本來會應用于孩子元素,但是如果body的孩子元素定義font屬性,則會被后者取代)。

解決辦法:共享樣式數據。(元素可以共享樣式數據的條件就是他們的狀態(tài)是”一致“的。)

webkit渲染

計算樣式并生成渲染對象的過程為attachment,每個dom節(jié)點有一個attach方法,attachment的過程是同步的,調用新節(jié)點的attach方法插入到dom樹中。
parser:解析, Render Tree:渲染樹 Layout:安排布局


webkit主流程.png

渲染過程中,webkit使用一個標志位標志所有頂層樣式都已經被加載完畢,如果dom元素進行attach時,css元素并沒有被加載完畢,則放置占位符,并在文檔中標記,當樣式表加載完畢,則重新進行計算。
說明,文檔的渲染還是要等待頂層css加載完畢。接下來的gecko應該也是需要等待頂層css加載完畢,否則“css規(guī)則樹”(見下文)無法建立啊

Gecko渲染

webkit渲染是一個元素與樣式規(guī)則匹配的過程,Gecko則需要構建樣式計算規(guī)則書,然后與dom樹對應生成樣式上下文數(及渲染樹)。例子:

<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>
               </p>
          </div>
          <div class=”err” id=”div2″>
                another error
          </div>
     </body>
</html>//規(guī)則 1. div {margin:5px;color:black} 2. .err {color:red} 3. .big {margin-top:3px} 4. div span {margin-bottom:4px} 5. #div1 {color:blue} 6. #div2 {color:green}


樣式規(guī)則樹.png

解釋一下:A:任意一個父級元素。B、E:代表元素選擇器,B指div,E指div下的span。C、G:代表類選擇器。D、F:代表:id選擇器。后面的123456,代表匹配的規(guī)則。


樣式上下文樹.png

解釋一下:當遇到一個dom節(jié)點,例如:第二個div,根據css解析結果,進行規(guī)則匹配發(fā)現符合126這條規(guī)則線,我們發(fā)現,當遇到第一個div時已經匹配過12這條規(guī)則線,所以只需為規(guī)則6新增一個節(jié)點至樣式上下文樹的div:F節(jié)點。樣式上下文樹,是元素匹配樣式的最終結果(原本是比例的也要換算成具體的px)。 Gecko利用樣式規(guī)則樹,有效的實現了樣式共享。Webkit沒有規(guī)則樹,則需要對css解析結果進行多次遍歷。出現多次的屬性將會被按照正確的級聯順序進行處理最后一個生效。

根據對計算樣式困難的理解,我們在編寫css樣式表時應該注意一下:

  1. dom深度盡量淺。

  2. 減少inline javascript、css的數量。

  3. 使用現代合法的css屬性。

  4. 不要為id選擇器指定類名或是標簽,因為id可以唯一確定一個元素。

  5. 不要給類選擇器指定標簽,類,代表具有一類屬性的標簽,不僅是一個,雖然可以實現,但是降低了效率。

  6. 避免后代選擇符,盡量使用子選擇符。原因:子元素匹配符的概率要大于后代元素匹配符。后代選擇符;#tp p{} 子選擇符:#tp>p{}

  7. 避免使用通配符,舉一個例子,.mod .hd *{font-size:14px;} 根據匹配順序,將首先匹配通配符,也就是說先匹配出通配符,然后匹配.hd(就是要對dom樹上的所有節(jié)點進行遍歷他的父級元素),然后匹配.mod,這樣的性能耗費可想而知.

http://www.cnblogs.com/ranyonsue/p/6734446.html