css、js阻塞

你們是否是會遇到這樣的一個問題,頁面加載速度過慢,瀏覽器老在轉圈圈,頁面部份內容須要花費較多的時間才能加載出來?javascript

要明白上述問題,咱們須要知道是什麼在阻塞頁面的渲染?css

一、瀏覽器如何渲染?

1.一、渲染引擎介紹
要先說明:Firebox 的渲染引擎是 Geoko,chrome的渲染引擎是wekit。本文使用的是chrome瀏覽器html

1.二、渲染的主要過程java

簡單介紹
瀏覽器解析 DOM 生成 DOM Tree, 結合CSS 生成的 CSS Tree, 最終組成 Render Tree,再渲染頁面。所以 在此過程 css 不會阻塞 DOM 解析。web

詳細介紹chrome

流程示意圖
clipboard.png瀏覽器

幾個概念:網絡

DOM Tree:瀏覽器將HTML解析成樹形的數據結構
CSS Rule Tree:瀏覽器將 CSS 解析成樹形的數據結構
Render Tree:DOM 和 CSSOM 合併後生成 Render Tree
layout:有了Render Tree, 瀏覽器已經知道網頁中有哪些節點、各個節點的CSS定義以及他們的從屬關係,從而去計算出每一個節點在屏幕中的位置
painting:按照算出來的規則,把內容畫到屏幕上
reflow(迴流):當瀏覽器發現某個部分發生了點變化影響了佈局,須要倒回去從新渲染,這個過程稱爲 reflow。reflow 會從 <html> 這個 root frame 開始遞歸往下,依次計算全部的結點尺寸和位置。reflow 是沒法避免的。目前界面上流行的一些效果,好比樹狀目錄的摺疊、展開(實質上是元素的顯示與隱藏)等,都將引發瀏覽器的 reflow。鼠標滑過、點擊等等... 只要這些行爲引發了頁面上某些元素的佔位面積、定位方式、邊距等屬性的變化,都會引發它內部、周圍甚至整個頁面的從新渲染
repaint(重繪):改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性時,屏幕的一部分要重畫,可是元素的幾何尺寸沒有變。數據結構

注意:
(1)display:none 的節點不會被加入Render Tree, 而 visibility:hidden則會;因此,若是某個節點最開始是不顯示的,設爲display:none 是更優的。
(2)display:none 會觸發reflow, 而 visibility:hidden 只會觸發repaint,由於位置沒有變化
(3)有些狀況下,好比修改了元素的樣式瀏覽器並不會馬上reflow 或 repaint 一次,而是會把這樣的操做積攢一批,而後作一次 reflow,這叫 異步 reflow 或 增量異步 reflow。可是在有些狀況下,好比resize 窗口,改變了頁面默認的字體等。對於這些操做,瀏覽器會立刻進行reflow。dom

webkit 的主要流程:

clipboard.png

Geoko 的主要流程:

clipboard.png

完整流程解析:
(1)瀏覽器將 HTML 解析成DOM 樹,當前節點的全部子節點都構建好後纔會去構建當前節點的下一個兄弟節點。
(2)將CSS 解析成 CSS Rule Tree
(3)根據DOM 樹 和 CSSOM 構造 Render Tree。注意,display:none 不會被掛載到 Render Tree 上面
(4)計算出每一個節點在屏幕中的位置
(5)繪製!
注意:渲染引擎將會盡量早的將內容呈現到屏幕上,並不會等到全部的html都解析完成以後再去構建和佈局render tree。它是解析完一部份內容就顯示一部份內容,同時,可能還在經過網絡下載其他內容

展現一下。display:none 與 visibility:hidden 在瀏覽器上的區別

display:none

clipboard.png

visibility:hidden

clipboard.png

二、阻塞渲染:CSS 與 javascript

討論資源的阻塞時,咱們要清楚,現代瀏覽器老是並行加載資源。例如,當HTML解析器(HTML Parser) 被腳本阻塞時,解析器雖然會中止構建DOM,但仍會識別該腳本後面的資源,並進行預加載

同時,因爲下面兩點:

  1. 默認狀況下,CSS 被視爲阻塞渲染的資源,這意味着瀏覽器不會渲染任何已處理的內容,直至CSSOM構建完畢
  2. javascript 不只能夠讀取和修改DOM 屬性,還能夠讀取和修改CSSOM 屬性

存在阻塞的 CSS 資源時, 瀏覽器會延遲javascript 的執行和 Render Tree 構建。

另外

  1. 當瀏覽器遇到一個script標記時,DOM 構建將暫停,直至腳本完成執行。
  2. javascript 能夠查詢和修改 DOM 與 CSSOM
  3. CSSOM 構建時,javascript 執行將暫停,直至 CSSOM 就緒。

因此,script 標籤的位置很重要。實際使用時,能夠遵循下面2個原則

  1. CSS 優先:引入順序上,CSS 資源優於javascript資源
  2. javascript 應儘可能少影響 DOM 的構建

2.一、CSS 會阻塞渲染嗎?

從瀏覽器的渲染原理來看,渲染引擎會將css 構建成 CSSOM Tree 而後再渲染頁面。也就是說,CSS 會 阻塞 頁面的渲染!可是,CSS 並不會阻塞 DOM 的解析。(由於須要具備 DOM 以及 CSSOM 纔會構建渲染樹

2.二、JS 會阻塞渲染嗎?

看如下代碼:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            div {
                width: 100px;
                height: 100px;
                background: lightgreen;
            }
        </style>
    </head>
    <body>
        <div>dsadddadas</div>
    </body>
</html>

下面這段代碼會在瀏覽器上面顯示一個綠色的盒子

clipboard.png

第2個例子,在頭部引入JS

// new_file.js
let arr = []
for    (let i = 0; i < 10000000; i++) {
    arr.push(i);
    arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector('div');
console.log(div);
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            div {
                width: 100px;
                height: 100px;
                background: lightgreen;
            }
        </style>
        <script src="js/new_file.js"></script>
    </head>
    <body>
        <div>box1</div>
        <div style="visibility: hidden;">box2</div>
        <div>box3</div>

    </body>
</html>

瀏覽器會先轉圈圈,最後在顯示 盒子

也就是說,爲何大部分程序都會將 js 放在 底部,css 放在頂部 就是爲了加速頁面的渲染

三、咱們如何改變阻塞現狀?defer 與 async

3.一、defer 與 async 的介紹

defer:js的加載不會阻塞頁面的渲染和資源的加載,defer 會按照本來js的順序執行。
async:js 的加載不會阻塞頁面的渲染和資源的加載,一旦加載到就會馬上執行。若是js先後有依賴性,最好不要用async。

3.二、defer 與 async的區別
3.2.一、相同點:

  • 加載文件時不阻塞頁面渲染
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title></title>
        <style>
            div {
                width: 100px;
                height: 100px;
                background: lightgreen;
            }
        </style>
        <script src="js/new_file.js" defer></script>
    </head>
    <body>
        
        <div>box1</div>
        <div>box3</div>

    </body>
</html>
// new_file.js
let arr = []
for    (let i = 0; i < 10000000; i++) {
    arr.push(i);
    arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector('div');
console.log(div);

會在一運行的時候,就在頁面中顯示2個盒子,所以不阻塞

  • 對於inline的script(內聯腳本)無效
<script>
    let arr = []
    for    (let i = 0; i < 10000000; i++) {
        arr.push(i);
        arr.splice(i%3, i%7 ,i %5);
    }    
    console.log("...")
</script>
<script async>
    console.log("async")
</script>
<script defer>
    console.log("defer")
</script>

clipboard.png

瀏覽器按順序打印!

  • 使用這兩個屬性的腳本中不能調用document.write 方法
// new_file.js
let arr = []
for    (let i = 0; i < 10000000; i++) {
    arr.push(i);
    arr.splice(i%3, i%7 ,i %5);
}
let div= document.querySelector('div');
console.log(div);
document.write('asdadasas');

clipboard.png

  • 有腳本的onload 事件回調

3.2.二、不一樣點:

  • 每個async 屬性的腳本都在它下載結束後以後馬上執行,同時會在window的load事件以前執行。
  • 每個defer屬性的腳本都是在頁面解析完畢以後,按照本來的屬性執行,同時會在document的DOMContentLoader以前執行

參(抄)考(襲)文章:
https://juejin.im/post/59c606... 原來 CSS 與 JS 是這樣阻塞 DOM 解析和渲染的

https://juejin.im/entry/59e1d... 瀏覽器的渲染:過程與原理

https://juejin.im/post/5a1229... script中defer和async的區別

https://www.cnblogs.com/Bonni... load/domContentLoaded事件、異步/延遲Js 與DOM解析

相關文章
相關標籤/搜索