第16章 頁面加載渲染和性能優化【這些會了,你就可以飛了】

返回章節目錄

目錄

1.頁面加載和渲染過程

頁面加載過程

渲染過程(1)

渲染過程(2)

構建DOM樹

構建CSSOM樹

運行JavaScript

創建Render樹

生成佈局

繪製(Painting)

把它們結合起來

window.onload和DOMContentLoaded 


 

由於篇幅原因,我決定將第12章分成上下兩部分。。。

1.頁面加載和渲染過程

一般來說,經常會被問到一個問題:在瀏覽器地址欄輸入URL,按下回車後究竟發生了什麼?

這個問題沒有標準答案,裏面的知識點足夠寫出一本書,甚至一本還不夠。就看你應聘對應的職位是什麼,那麼這個問題的答案就會有所偏向。接下來我們來看看就前端而言需要掌握哪些

 

頁面加載過程

一般來說,頁面資源分爲3類

html代碼

媒體文件,如圖片、視頻等

javascript、css

 

頁面加載基礎知識解釋:

1.DNS解析: 域名-->IP地址

爲什麼要域名?僅僅是因爲 IP 不好記嗎?

不是的,同一個域名對應的IP在不同的區域是不一樣的,因爲大型網站做分區域的IP均衡代理。你在北京訪問百度和你在深圳訪問百度的域名是一樣的都是baidu.com,但是DNS解析出來的 IP 是不一樣的,如果你在北京直接訪問深圳的 IP 就會慢很多

在瀏覽器訪問網站的時候,實際還是訪問 IP,域名解析服務(DNS)會根據地域去解析成不同的 IP 讓你的網站訪問的更快

綜上所述,使用域名不僅僅是因爲容易記住,而且是網頁訪問更快,因爲DNS會解析出來的 IP 距離你很近。

 

2.瀏覽器根據 IP 地址向服務器發起http請求

瀏覽器只是發起方,真正的核心模塊還是操作系統,操作系統裏有一些可以發起網絡請求的服務,調用操作系統的服務,然後操作系統去發起http請求。這裏面還有建立連接的三次握手過程,這個可以自行搜索。

 

3.服務器處理http請求,並返回給瀏覽器

至於服務器怎麼處理http請求這裏不講,返回的頁面資源就可能有html代碼,媒體文件,如圖片、視頻等,javascript、css等等。

 

渲染過程(1)

請求的是頁面返回html代碼

1.根據HTML代碼生成DOM Tree(文本代碼生成樹結構)

2.根據CSS生成CSSOM

3.將DOM Tree和CSSOM整合形成Render Tree(渲染樹)

只根據DOM Tree是無法渲染的,其標籤的CSS屬性是在CSSOM Tree裏面的,可以將Render Tree理解爲掛着CSS屬性的DOM Tree

 

渲染過程(2)

1.根據Render Tree渲染頁面

2.遇到<script>則暫停渲染,優先加載並執行JS代碼,完成再繼續

(JS操作和渲染頁面操作是共用一個線程的,因爲JS可能改變DOM結構而改變Render Tree的結構,所以遇到<script>就暫停渲染,否則渲染了可能沒用,Render Tree變了)

3.直到把Render Tree渲染完成

 

對於渲染感興趣的,請參見下面大佬的文章,這是我從付費文章專欄copy過來的,爲了看所有文章花了我199大洋開年會。

接下來講的理解關鍵渲染路徑的內容來自大漠老師的收費文章,每篇文章3.99元,在這裏免費送給大家學習。

當瀏覽器從服務器接收到一個HTML頁面的請求時,到屏幕上渲染出來要經過很多個步驟。瀏覽器完成這一系列的運行,或者說渲染出來我們常常稱之爲“關鍵渲染路徑”(Critical Rendering Path)。

理解CRP(Critical Rendering Path)相關的知識可以更好的提高網站的性能。那麼理解我們從下面六個部分來理解CRP相關的知識:

構建DOM樹

DOM(文檔對象模型)樹是一個完全解析的HTML頁面對象。從<html>根元素開始到頁面中每個元素和文本的節點。元素嵌套在其他元素內則表示爲子節點,每個節點包含完整的屬性元素。例如一個<a>元素,它就有與之相關的href節點。

來看下面這個簡單的DOM示例:

<html>  
<head>  
  <title>Understanding the Critical Rendering Path</title>
  <link rel="stylesheet" href="style.css">
</head>  
<body>  
  <header>
      <h1>Understanding the Critical Rendering Path</h1>
  </header>
  <main>
      <h2>Introduction</h2>
      <p>Lorem ipsum dolor sit amet</p>
  </main>
  <footer>
      <small>Copyright 2017</small>
  </footer>
</body>  
</html>  

這將會創建一個像下面這樣的DOM樹:

HTML比較好的是它可以執行部分。完整的文檔不需要加載的內容會在頁面的開始呈現。然而,比如CSS和JavaScript可以阻止頁面的呈現。

構建CSSOM樹

CSSOM(CSS對象模型)是一個表示DOM樣式的對象。它類似於DOM,但是是每個節點相關的樣式,包括他們是否顯式聲明或隱式繼承。

比如,在style.css文件中對上面的DOM有這樣的一些樣式:

body { font-size: 18px; }

header { color: plum; }  
h1 { font-size: 28px; }

main { color: firebrick; }  
h2 { font-size: 20px; }

footer { display: none; }  

這將會構建像下面這樣的一個CSSOM樹:

CSS被認爲是“渲染阻塞資源”。這意味着渲染樹(見下文)的構建離不開延續一個資源的解析完成度。不像HTML,CSS不能只用部分,因爲CSS是具有繼承層疊特性。文檔後面定義的樣式可以覆蓋前面定義的樣式。所以,如果我們開始使用CSS時,之前的樣式表會被認爲已經全部解析完。這也意味着CSS必須充分解析才能繼續下一個階段。

如果只運用於當前設備,CSS文件只被認爲阻塞。<link rel="stylesheet">標籤可以接受一個media屬性,可以用來指定樣式適用於何種媒體。例如,我們有一個樣式表,它的media屬性設置爲orientation:landscape時,如果在portrait模式下查看頁面,那麼這個樣式表不會被認爲是一個阻塞資源。

CSS還會阻塞JavaScript。那是因爲JavaScript文件必須要等CSSOM構建完纔會執行。

運行JavaScript

JavaScript被認爲是一個“解析器阻塞資源”。這意味着解析的HTML文檔本身就會被JavaScript阻塞。

當解析器讀到<script>標記,不管是內部的還是外部的,它停止獲取(如果是外部的)並運行它。這個爲什麼,如果我們有一個JavaScript文件,該文件引用文檔中的元素,那麼它必須放在這個元素的後面。

爲了避免JavaScript解析器造成阻塞,可以添加async屬性,異步加載它。

<script async src="script.js">  

創建Render樹

Render樹是DOM和CSSOM的組合。這個樹代表最終在頁面上呈現的東西。這意味着它只抓住了可見的內容,將不包括設置了hidden的元素和CSS設置了display:none的元素。

使用上面的DOM和CSSOM,構建的Render樹如下圖所示:

生成佈局

佈局根據CSS樣式設置的大小來決定頁面在窗口中的尺寸。視窗的大小是由<head>viewport標記來決定,如果沒有提供這個標記,默認使用的視窗寬度是980px

例如,常見的設置視窗的meta標籤,指定的大小對應設備寬度:

<meta name="viewport" content="width=device-width,initial-scale=1">  

如果用戶訪問頁面寬度是基於設備的寬度1000px。那一半的視窗就是500px10vw就是100px

繪製(Painting)

最後就是繪製(Painting)步驟,把頁面可見的內容轉化爲像素在屏幕上顯示。

繪製需要多少時間這取決於DOM的大小以及應用的樣式。有一些樣式需要更多的執行時間。例如,一個複雜的漸變背景圖像比一個單一顏色背景繪製需要更多的時間。

把它們結合起來

我們可以在DevTools中看到關鍵渲染路徑的過程。在Chrome瀏覽器中,點擊Timeline(現在是Performance)選項。

拿文章前面的示例(這裏添加了<script>標籤):

<html>  
<head>  
  <title>Understanding the Critical Rendering Path</title>
  <link rel="stylesheet" href="style.css">
</head>  
<body>  
  <header>
      <h1>Understanding the Critical Rendering Path</h1>
  </header>
  <main>
      <h2>Introduction</h2>
      <p>Lorem ipsum dolor sit amet</p>
  </main>
  <footer>
      <small>Copyright 2017</small>
  </footer>
  <script src="main.js"></script>
</body>  
</html>  

如果我們看頁面的加載日誌,將看到如下這樣的數據:

  • 發送請求:發送GET,請求index.html
  • 解析HTML併發送請求:解析HTML並且構建DOM樹,發送GET請求,請求style.cssmain.js
  • 解析樣式:根據style.css創建CSSOM
  • 腳本評估main.js評估
  • 佈局:基於HTML中的視窗meta生成佈局
  • 繪製:繪製頁面

如果覺得不過癮,我們繼續來看看《深入淺出Vue.js》的作者劉博文寫的一篇文章關鍵渲染路徑

 

 

window.onload和DOMContentLoaded 

如果在渲染的時候遇到img標籤鏈接的圖片還沒下載完也會繼續渲染,會把這個位置空出來,等到圖片下載完成插入就行,圖片的大小未指定可能引起reflow,比如圖片過大把旁邊內容撐下去了,所以最好圖片設置寬和高來避免重新渲染。

下面我們來看一個例子演示load和DOMContentLoaded執行時機

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>運行環境 演示</title>
    </head>
    <body>
        <p>一段文字 1</p>
        <p>一段文字 2</p>
        <p>一段文字 3</p>
        <img
            id="img1"
            src="https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1570191150419&di=37b1892665fc74806306ce7f9c3f1971&imgtype=0&src=http%3A%2F%2Fimg.pconline.com.cn%2Fimages%2Fupload%2Fupc%2Ftx%2Fitbbs%2F1411%2F13%2Fc14%2F26229_1415883419758.jpg"
        />

        <script src="./index.js"></script>
    </body>
</html>

index.js

const img1 = document.getElementById('img1')
img1.onload = function () {
    console.log('img loaded')
}

window.addEventListener('load', function () {
    console.log('window loaded')
})

document.addEventListener('DOMContentLoaded', function () {
    console.log('dom content loaded')
})

控制檯的打印依次是

dom content loaded
img loaded
window loaded

就算是我Throttling是設置的Slow 3G,但是仍然等網頁加載出來的時候控制檯嗖的一下就結束了,現在的瀏覽器這麼屌了?

所以只能在數值上給大家一個直觀的感受,下面是Slow 3G的結果,而online的結果DOMContentLoaded比img1的回調早了1.2ms,img1的onload比window的load回調早了0.3ms。

根據我Performance裏面的數據

第一個Event是DOMContentLoaded的事件

第二個Event是img1的事件

第三個Event是window的load事件

根據結果顯示,Function Call的時候我們可以看到DOMContentLoaded和window的load調用的是匿名函數anonymous,而img1.οnlοad=funtion(){...}是我們指定的onload函數,這和代碼也能對應的上

注意,DOMContentLoaded是包含在ParseHTML中的

而ParseHTML結束後纔會有img1的onload和window的load

擴展學習:

幸好document.readyState還是有點錘子用的

document.readyState

 

 

關注、留言,我們一起學習。

 

===============Talk is cheap, show me the code================

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章