前言
瀏覽器的渲染原理是每個前端開發工程師的必修課,網絡上也有很多這方面的文章,但大多數只是告訴你瀏覽器表現就是這樣的,並沒有一個直觀的認識,讓人讀完就瞬間忘卻。
所以小編決定通過node實踐來模擬瀏覽器渲染時的一些場景,對市面上的文章的一些觀點進行驗證,希望能夠加深自己的認識。
動手實踐之前,先了解下瀏覽器的渲染機制的一些基本知識,如下圖所示:
DOM:Document Object Model,瀏覽器將HTML解析成樹形的數據結構,簡稱DOM。
CSSOM:CSS Object Model,瀏覽器將CSS代碼解析成樹形的數據結構
Render Tree:DOM 和 CSSOM 合併後生成 Render Tree(Render Tree 和DOM一樣,以多叉樹的形式保存了每個節點的css屬性、節點本身屬性、以及節點的孩子節點)
Node實例推導
爲了方便觀察靜態資源加載情況和渲染細節,用node搭建一個靜態服務器,代碼如下:
const http = require('http');
const fs = require('fs');
let hostname = '127.0.0.1';
let port = 8080;
let server = http.createServer((req, res) => {
console.log(req.url);
if (req.url == '/a.js') {
fs.readFile('src/a.js', (err, data) => {
res.writeHead(200, {'Content-Type': 'text/javascript'});
setTimeout(() => {
res.write(data);
res.end();
}, 10000) // 延遲 10s 再返回 a.js 文件
})
} else if(req.url == '/b.js') {
fs.readFile('src/b.js', (err, data) => {
res.writeHead(200, {'Content-Type': 'text/javascript'});
res.write(data);
res.end();
})
} else if(req.url == '/index.html') {
fs.readFile('src/index.html', (err, data) => {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
})
} else if (req.url == '/style.css') {
fs.readFile('src/style.css', (err, data) => {
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(data);
res.end();
})
}
})
server.listen(port, hostname, () => {
console.log(`server has already started: ${hostname}:${port}`)
})
從上面代碼中,我們知道a.js
的請求是延遲10s才響應返回的
啓動服務器,在瀏覽器打開 http://127.0.0.1:8080/index.html
1. 驗證問題一:外部靜態資源是如何請求的?
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>
<script src='http://127.0.0.1:8080/a.js'></script>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<script src='http://127.0.0.1:8080/b.js'></script>
<p>222222</p>
<p>3333333</p>
</body>
</html>
刷新頁面看下Timeline,如下圖所示:
從上面的動畫中我們能得到如下幾點:
- 頁面10s之後才展示,所以
a.js
阻塞了頁面的渲染展示; - 第一次
Parse HTML
解析時,a.js
造成了阻塞,預解析就去發起請求style.css,b.js
,所以三個靜態資源看起來是一起請求的,這一點需要看下後面的驗證; - 三個靜態資源都是在解析
html
標籤時發起請求的,這一點也需要再驗證。
下面修改一下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>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>重複</p>
<p>重複</p>
....
....重複5000行
<script src='http://127.0.0.1:8080/a.js'></script>
<script src='http://127.0.0.1:8080/b.js'></script>
<p>222222</p>
<p>3333333</p>
</body>
</html>
刷新頁面,會得到如下TimeLine圖片:
從圖中我們可以得知:
- 當html內容太多的時候,瀏覽器需要分段接收,解析的時候也要分段解析;
- 靜態資源並不是同時請求的,也不是解析到指定標籤的時候纔去請求的,瀏覽器會自行判斷,如果當前操作比較耗時,就會請求後面的資源,所以靜態資源請求的時機是無法確定的,瀏覽器有多重兼容處理方案。
2. 驗證問題二:JS 對 HTML 的解析和渲染方面的影響
// 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>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<script src='http://127.0.0.1:8080/b.js'></script>
<script src='http://127.0.0.1:8080/a.js'></script>
<p>222222</p>
<p>3333333</p>
</body>
</html>
刷新頁面,執行過程如下圖動畫所示:
從執行過程中我們發現,由於a.js
的延遲返回,a.js
沒有下載完成,Dom樹解析構建過程被阻塞停止,但a.js
前面解析出來的html標籤被渲染展示出來了。當a.js
下載完成後,繼續解析後面的標籤並渲染展示。當然,瀏覽器不是解析一個標籤就繪製顯示一次,當遇到阻塞或者比較耗時的操作的時候纔會先繪製一部分解析好的。
綜上可知,JS會阻塞頁面的解析和渲染,這也是JS文件常被放在頁面底部的原因
3.驗證問題三:CSS 對頁面渲染解析的影響
修改服務器node代碼,將style.css
延遲10s再返回
// style.css
p:nth-child(1) {
color: red;
}
p:nth-child(2) {
color: blue;
}
p:nth-child(3) {
color: green;
}
// 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>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
</body>
</html>
刷新頁面,執行過程如下圖動畫所示:
從上面執行流程中發現,style.css
延遲10s後返回,頁面dom 樹被正常解析構建,但是沒有被渲染展示。當css下載完成後,頁面被被渲染並且樣式生效。
修改index.html
中style.css
的位置,將其移到body最下方,代碼如下:
<!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 id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</body>
</html>
刷新頁面,執行過程如下圖動畫所示:
從動畫中發現,style.css
的延遲加載,沒有阻塞前面的dom樹的解析構建和渲染,渲染的P
元素沒有樣式。當style.css
下載完成後,元素的樣式生效並展示。
綜上可知,CSS
不阻塞dom樹的構建解析,只會阻塞其後面元素的渲染,不會阻塞其前面元素的渲染。
如果將CSS放到頁面底部,會先渲染出不帶樣式的頁面內容,等CSS加載樣式會生效,頁面看着會有抖動的現象,所以CSS一般放在head
中。
4. 圖片對頁面渲染解析的影響
修改node代碼,對code.png
做延時處理,具體代碼如下:
<!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>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<img src="./code.png"/>
<p>222222</p>
<p>3333333</p>
</body>
</html>
刷新頁面,執行過程如下圖動畫所示:
從動畫中可以發現,圖片既不阻塞解析,也不阻塞渲染。
總結
根據上面的實例,我們得到如下幾點重要的結論:
- 靜態資源並不是同時請求的,也不是解析到指定標籤的時候纔去請求的,瀏覽器會自行判斷;
- JS 會阻塞頁面的解析和渲染,同時瀏覽器也存在預解析,遇到阻塞可以繼續解析下面的元素;
- CSS`不阻塞dom樹的構建解析,只會阻塞其後面元素的渲染,不會阻塞其前面元素的渲染;
- 圖片既不阻塞解析,也不阻塞渲染。
瀏覽器相對於開發者而言,如同黑盒,學習瀏覽器渲染方面的知識時,可以從瀏覽器源碼或者瀏覽器提供的調試工具兩方面進行學習,網上的一些文章總結最好動手實踐下,這樣印象會更深刻。
參考
https://segmentfault.com/a/1190000007766425
掃一掃 關注我的公衆號【前端名獅】,更多精彩內容陪伴你!