1. 程式人生 > >頁面渲染與其效能的提升

頁面渲染與其效能的提升

  做web開發,我個人覺得必須要弄清楚瀏覽器的渲染過程,否則我們很難進行前端優化。   我今天就簡單說一下頁面載入和前端優化。 頁面載入 我按照最簡單的方式進行描述,實際上更復雜,不管是在瀏覽器端還是服務端,比如dns解析,代理伺服器,負載均衡器等等。

1、使用者訪問網頁,傳送一個http請求到網路伺服器。 2、網路伺服器(應用伺服器)解析請求,傳送請求給資料庫伺服器。 3、資料伺服器返回資料給網路伺服器,網路伺服器解析資料,並生成html檔案內容放入http response中,返回給瀏覽器。 4、瀏覽器解析http response。 5、瀏覽器建立DOM樹。 6、瀏覽器下載css,並應用在DOM樹上,進行渲染。 7、瀏覽器下載js,並解析執行js。 缺陷 以上整個流程中,如果其中任何一個流程出現問題,都不能順利的渲染頁面。 服務端:   網路伺服器:無法獲取到資原始檔(404),或者由於併發的原因暫時無法處理你的請求(最常見的500錯誤),你的瀏覽器會長時間處於空白狀態,直到伺服器返回狀態,或者進行超時處理。   資料層:如果伺服器停止,或忙於處理大資料等等,長時間無法返回資料給網路伺服器,那麼網路伺服器一直處於等待狀態中,如果請求量達到最大值,那麼後面的請求都被堵塞,從而無法及時返回內容給瀏覽器。 客戶端:   JavaScript:如果你的js寫在body中的div裡,而且這個js執行非常複雜的邏輯,那麼整個頁面處於等待狀態中。
  不論js程式碼是內聯還是包含在一個不相干的外部檔案中,頁面下載和解析過程肯定會停下,等待指令碼執行完成這些處理,然後才能繼續進行。——大多數瀏覽器使用單程序處理JavaScript的多個任務,同一時間只能有一個任務執行。

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

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

  CSS:可以同時下載多個CSS檔案。       如果我們把CSS樣式放在頁面底部,雖然使頁面內容能更快的載入(因為將載入css 檔案的時間放在最後,從而使頁面內容先顯示出來),但這樣的內容是沒有樣式的,在CSS檔案載入進來後,瀏覽器再對DOM使用樣式,會出現我們常說的“無樣式之閃爍”。       更討厭的是,上下都放置CSS樣式,瀏覽器會首先按照上面的進行渲染,等到下面的樣式上來,再按照下面的樣式進行迴流和重繪,使用者感覺很差。
注意兩個詞“repaint"和"reflow"。   repaint(重繪)是在一個元素的外觀被改變,但沒有改變佈局的情況下發生。——如果只是改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性,將只會引起瀏覽器repaint。   reflow(迴流):瀏覽器發現某個部分發生了點變化影響了佈局,需要倒回去重新渲染,這個回退的過程就叫回流。
總結:以上兩種嚴重影響使用者體驗,會無意識的流失使用者。  

解決方案  服務端:方式比較多,可以從架構上說(這個內容太多了,什麼負載均衡了,什麼快取了,什麼主從了),但是今天主要討論語言層面。     我們可以使用逐步返回內容的方式,輸送資料給瀏覽器,如我們可以使用php的flush,把整個head部分,半個body加一部分div返回給瀏覽器,進行渲染,然後把其他部分逐步輸送到瀏覽器。     我們可以在服務端使用多執行緒或多程序的方式併發去進行資料處理。如php常見的 複製程式碼
do {
  $mrc = curl_multi_exec($mh, $active);
}while($mrc==CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK){
  if (curl_multi_select($mh) != -1){
    do {
      $mrc = curl_multi_exec($mh,$active);
    }while($mrc==CURLM_CALL_MULTI_PERFORM);
  }
}
複製程式碼

或者

複製程式碼
<?php
while (count($sockets)) {
    $read = $write = $sockets;
    $n = stream_select($read,$write, $e, $timeout);
    if ($n > 0) {
        foreach ($read as $r) {
            $id = array_search($r, $sockets);
            $data = fread($r, 8192);
            if (strlen($data) == 0) {
                fclose($r);
                unset ($sockets[$id]);
            }else {
                $retdata[$id] .= $data;
            }   
        }   
        $retdata[$id] = preg_replace('/^HTTP(.*?)\r\n\r\n/is',<em>, $retdata[$id]);</em>
            foreach ($write as $w) {
                if (!is_resource($w))continue;
                $id = array_search($w, $sockets);
                fwrite($w, "GET /" . $url[$id] . "HTTP/1.0\r\nHost: " . $hosts[$id] ."\r\n\r\n");
                $status[$id] = 1;
            }   
    }else {
        break;
    }   
}
複製程式碼

JavaScript:

  1、把指令碼進行壓縮(移除不必要的字元,註釋以及空行)。

  2、對部分js檔案進行合併,以減少http的請求個數,以減少伺服器端的壓力——但是要量力而行,因為如果你的js檔案很大,下載很慢的話,很多功能都不能正常進行,我們可以按照業務進行合併。

  3、使用外部js檔案。因為現在很多瀏覽器都有快取,明顯會減少http請求數。

  4、將指令碼放在頁面底部。先讓使用者看到內容,然後再載入js,這樣使用者會感覺頁面載入速度很快。

CSS:

  1、合併多個css檔案,以減少http的請求個數,以減少伺服器端的壓力。

  2、使用外部css檔案。主要原因是瀏覽器快取,以減少http請求。

  3、放在頁面頂部(head標籤處),防止出現“無樣式內容的閃爍”。

怎樣儘可能的縮短瀏覽器上頁面渲染的時間,可以從以下幾個方面入手:
1.寫出高效的css程式碼 
2.避免使用css表示式 
3.把css檔案放在頁面頂部 
4.指定頁面圖片的尺寸 
5.頁面頭部標明文件編碼
 
一,寫出高效的css程式碼
首先弄清瀏覽器解析html程式碼的過程:構建一個dom樹,頁面要顯示的各元素都會建立到這個dom樹當中。每當一個新元素加入到這個dom樹當中,瀏覽器便會通過css引擎查遍css樣式表,找到符合該元素的樣式規則應用到這個元素上。css引擎查詢樣式表,對每條規則都按從右到左的順序去匹配。
瞭解過程後,我們可以看出可以從兩方面優化我們的css程式碼:1,定義的css樣式規則條數越少越好,所以趕緊刪除css檔案中不必要的樣式定義;2,優化每條規則的選擇符書寫方式,儘量讓css引擎一看就知道這個規則是否需要應用到當前這個元素上,讓引擎少走不必要的彎路。
如以下幾種效率不高的css書寫方式:
a, 用萬用字元作為關鍵選擇符(關鍵選擇符指的是每條規則最右側的選擇符)
複製程式碼程式碼如下:
body * {...} 
.hide-scrollbars * {...} 
b, 用標籤做關鍵選擇符
複製程式碼程式碼如下:
ul li a {...}
#footer h3 {...}
* html #atticPromo ul li a {...}
c, 畫蛇添足的寫法
複製程式碼程式碼如下:
ul#top_blue_nav {...}
form#UserLogin {...} 
d, 給非連線標籤新增 :hover 偽類,這會對用了strict doctype的頁面在IE7和IE8下變的很慢。
複製程式碼程式碼如下:
h3:hover {...}
.foo:hover {...}
#foo:hover {...}
div.faa :hover {...}
優化建議:
a, 避免使用萬用字元;
b, 讓css引擎快速辨別該規則是否適用於當前元素:多用id或class選擇符,少用標籤選擇符;
c, 不要畫蛇添足把id和class或標籤和class等連著寫;
d, 儘量避免使用後代選擇符,去除不必要的祖先元素,可以考慮使用class選擇符來替換後代選擇符;
複製程式碼程式碼如下:
/*給無序和有序的li定義不同顏色,你可能會這樣寫:*/
ul li {color: blue;}
ol li {color: red;}
/*給li新增class,這樣定義效率會更高:*/
.unordered-list-item {color: blue;}
.ordered-list-item {color: red;}
e, 避免給非連線標籤新增 :hover 偽類。
二,避免使用css表示式
css表示式僅在ie瀏覽器下才起作用,微軟已在ie8後不推薦使用,因為它會嚴重影響頁面效能:任何時候,不管任何一個事件被觸發,例如視窗的 resize 事件,滑鼠的移動等等,css表示式都會重新計算一遍。
三,把css檔案放在頁面頂部
把外聯或內聯樣式表放在body部分會影響頁面渲染的速度,因為瀏覽器只有在所有樣式表下載完成後才會繼續下載頁面其他內容。另外,內聯樣式表(放在<style>內的樣式)有可能會引起頁面重新渲染或顯示隱藏頁面中的某些元素,建議不要使用內聯樣式表。
四,指定頁面圖片的尺寸
指定頁面圖片尺寸,要符合圖片的真實尺寸(不要通過指定尺寸來縮放圖片),可以避免尺寸改變導致的頁面結構效果的變化,所以對加快頁面渲染速度有益。
五,頁面頭部標明文件編碼
HTML文件是以包含文件編碼資訊的資料流方式在網路間傳輸。頁面的編碼資訊一般會在HTTP響應的頭部資訊或在文件內的HTML標記中指明。客戶端瀏覽器只有在確定了頁面編碼後才能正確的渲染頁面,所以在繪製頁面或執行任何的javascript程式碼前,大部分的瀏覽器(ie6、ie7、ie8除外)都會緩衝一定位元組的資料來從中查詢編碼資訊,不同的瀏覽器當中預緩衝的位元組數是不一樣的。如果瀏覽器在接收到了設定的預緩衝資料量後還沒有找到頁面的編碼資訊,便會根據各自指定的預設編碼開始渲染頁面,如果這時再獲取到頁面編碼資訊,而又跟現在所用編碼不一致,那整個頁面就得重新渲染,某些情況下甚至需要重新獲取資料。所以,對於大小超過1KB的頁面(根據在各瀏覽器的測試情況,預緩衝資料量最多的也就1KB)應當儘早標明編碼資訊。
優化建議:
a, 儘量在HTTP頭部資訊中標明頁面編碼(這個需要在伺服器端設定)。像Firefox瀏覽器,如果在HTTP頭部資訊就獲取到了編碼資訊,便會預緩衝更少的資料從而減少不必要的資料緩衝時間;
b, 在HTML的 <head> 部分標明編碼資訊;
c, 要習慣給文件指定編碼;
d, 給頁面指定的編碼要符合頁面實際編碼;如果你在HTTP頭部資訊和HTML標記中同時指定了編碼,需確保編碼資訊一致。