從 Node 實例推導瀏覽器的渲染機制

前言

瀏覽器的渲染原理是每個前端開發工程師的必修課,網絡上也有很多這方面的文章,但大多數只是告訴你瀏覽器表現就是這樣的,並沒有一個直觀的認識,讓人讀完就瞬間忘卻。

所以小編決定通過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,如下圖所示:

從上面的動畫中我們能得到如下幾點:

  1. 頁面10s之後才展示,所以a.js阻塞了頁面的渲染展示;

  2. 第一次Parse HTML解析時,a.js造成了阻塞,預解析就去發起請求style.css,b.js,所以三個靜態資源看起來是一起請求的,這一點需要看下後面的驗證;

  3. 三個靜態資源都是在解析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圖片:

從圖中我們可以得知:

  1. 當html內容太多的時候,瀏覽器需要分段接收,解析的時候也要分段解析;

  2. 靜態資源並不是同時請求的,也不是解析到指定標籤的時候纔去請求的,瀏覽器會自行判斷,如果當前操作比較耗時,就會請求後面的資源,所以靜態資源請求的時機是無法確定的,瀏覽器有多重兼容處理方案。

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.htmlstyle.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>

刷新頁面,執行過程如下圖動畫所示:

從動畫中可以發現,圖片既不阻塞解析,也不阻塞渲染。

總結

根據上面的實例,我們得到如下幾點重要的結論:

  1. 靜態資源並不是同時請求的,也不是解析到指定標籤的時候纔去請求的,瀏覽器會自行判斷;

  2. JS 會阻塞頁面的解析和渲染,同時瀏覽器也存在預解析,遇到阻塞可以繼續解析下面的元素;

  3. CSS`不阻塞dom樹的構建解析,只會阻塞其後面元素的渲染,不會阻塞其前面元素的渲染;

  4. 圖片既不阻塞解析,也不阻塞渲染。

瀏覽器相對於開發者而言,如同黑盒,學習瀏覽器渲染方面的知識時,可以從瀏覽器源碼或者瀏覽器提供的調試工具兩方面進行學習,網上的一些文章總結最好動手實踐下,這樣印象會更深刻。

參考

https://segmentfault.com/a/1190000007766425

❤️ 看完三件事

如果你覺得這篇內容對你挺有啓發,我想邀請你幫我三個小忙:

  1. 點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)

  2. 關注我的博客 https://github.com/SHERlocked93/blog,讓我們成爲長期關係

  3. 關注公衆號「前端下午茶」,持續爲你推送精選好文,也可以加我爲好友,隨時聊騷。

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