目錄
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
。那一半的視窗就是500px
,10vw
就是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.css
和main.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
擴展學習:
關注、留言,我們一起學習。
===============Talk is cheap, show me the code================