js和css的加載造成阻塞


瀏覽器渲染原理圖:

 

 

bar.js

var count_bar = 0;
var start_bar = new Date();
for(var i=0;i<100000;i++){
  for(var j=0;j<10000;j++){
    count_bar++;
  }
}
var end_bar = new Date();
console.log(end_bar -  start_bar,'bar');

 foo.js

var count_foo = 0;
var start_foo = new Date();
for(var i=0;i<100000;i++){
  for(var j=0;j<10000;j++){
    count_foo++;
  }
}
var end_foo = new Date();
console.log(end_foo - start_foo,'foo');

 ress.js

var count_ress = 0;
var start_ress = new Date();
for(var i=0;i<100000;i++){
  for(var j=0;j<10000;j++){
    count_ress++;
  }
}
var end_ress = new Date();
console.log(end_ress - start_ress,'ress');

 demo.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>Document</title>
    <script src="js/bar.js"></script>
    <script src="js/foo.js"></script>
    <script src="js/ress.js"></script>
</head>

<body>
    <div id="dd">
        div 1
    </div>
    <p>paragraph</p>

    <div>
        div 2
    </div>
</body>

</html>

 來自於safari的截圖

1.現代瀏覽器會並行加載js文件,參見截圖的start time列,但是按照書寫順序執行代碼

2.加載或者執行js時會阻塞對標簽的解析,也就是阻塞了dom樹的形成,只有等到js執行完畢,瀏覽器才會繼續解析標簽。沒有dom樹,瀏覽器就無法渲染,所以當加載很大的js文件時,可以看到頁面很長時間是一片空白

之所以會阻塞對標簽的解析是因為加載的js中可能會創建,刪除節點等,這些操作會對dom樹產生影響,如果不阻塞,等瀏覽器解析完標簽生成dom樹后,js修改了某些節點,那么瀏覽器又得重新解析,然后生成dom樹,性能比較差

修改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>Document</title>
    <script src="js/bar.js"></script>
    <script src="js/foo.js"></script>
    <script src="js/ress.js"></script>
</head>
<body>
    <div id="dd">
        div 1
    </div>
    <p>paragraph</p>

    <div>
        div 2
    </div>
    <img src="images/beauty.png" alt="" onload="console.log('image loaded')">
    
    <script>
        document.addEventListener("DOMContentLoaded",function(){
            console.log("dom content loaded");
        })
        window.onload = function(){
            console.log('resources loaded');
        }
    </script>
</body>

</html>

 來自chrome的截圖

1.文檔解析完成時觸發domcontentloaded事件。瀏覽器逐行解析,遇到</html>表示解析完成

2.當所有的資源都加載完后觸發window的load事件。

3.監聽資源加載完成有四種方式

  3.1 window.onload = function(){....}

  3.2 window.addEventListener("load",function(){....});

  3.3 document.body.onload = function(){....}

  3.4 <body onload = "....">

錯誤方式: document.body.addEventListener('load',function(){....});

這塊各瀏覽器表現沒有統一標准,推薦使用window.onload來監聽,比較保險

給script標簽添加defer屬性,僅限外部腳本

<script src="js/bar.js" defer></script>

 結果:

1.defer屬性表示延遲腳本的執行,等到整個文檔解析完再執行

2.defer屬性能延遲執行,但是不會延遲下載,瀏覽器遇到script就立即下載腳本

3.文檔解析完成時,腳本被執行,此時也會觸發domcontentloaded事件,優先執行腳本

4.多個標簽添加defer屬性,執行順序仍然是按書寫順序

給script標簽添加async屬性,僅限外部腳本

<script src="js/bar.js" async></script>
<script src="js/foo.js" async></script>
<script src="js/ress.js" async></script>

 結果

chrome:

 safari:

firefox:

1.async屬性的作用是讓瀏覽器異步加載腳本文件。在加載腳本文件的時候,瀏覽器能繼續標簽的解析。

2.異步腳本一定會在load事件之前執行,但可能會在domcontentloaded事件之前或者之后執行。

3.異步腳本之間的執行順序不確定,可能不會按照書寫順序執行

刪除script外部鏈接

demo.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>Document</title>
</head>

<body>
    <div>
        before
    </div>
    <script>
        var count = 0;
        for (var i = 0; i < 100000; i++) {
            for (var j = 0; j < 10000; j++) {
                count++;
            }
        }
        console.log(count);
    </script>
    <div id="dd">
        div 1
    </div>
    <p>paragraph</p>

    <div>
        div 2
    </div>
    <img src="images/beauty.png" alt="" onload="console.log('image loaded')">

    <script>
        document.addEventListener("DOMContentLoaded", function () {
            console.log("dom content loaded");
        })
        window.onload = function () {
            console.log('resources loaded');
        }
    </script>
</body>

</html>

 結果是:

1.持續一段空白頁面后,才有東西出來。也就是說頁面元素的渲染是整體的。瀏覽器構建完整個DOM樹后渲染,不會因為<div>before</div>出現在<script>....</script>之前,before就會先出現。

2.通常把script內容放在body最后,這樣腳本文件不會阻止其他資源的下載,但是由於DOM的解析完成是依據是否遇到</html>標簽,那么瀏覽器當遇到script標簽時會立即執行里面的代碼,導致DOM的解析被阻塞。

 css對渲染的阻塞

<html lang="en">

<head>
    <title>css阻塞</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
   <script>
       console.log('before css');
       document.addEventListener("DOMContentLoaded",function(){
           console.log("content loaded");
           f();
       })
       function f(){
           console.log(document.querySelectorAll("h1"));
       }
   </script>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0-alpha.6/css/bootstrap.css" rel="stylesheet">
    <link href="http://netdna.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.css" rel="stylesheet">
    <link href="http://apps.bdimg.com/libs/bootstrap/2.3.2/css/bootstrap-responsive.css" rel="stylesheet">

    <script>
        console.log("after css");
    </script>
</head>

<body>
    <h1>這是紅色的</h1>
    <h1>head</h1>

</body>

</html>

 safari截圖:

 

 輸出結果:

1.css文件是並行下載的

2.css的下載會阻塞后面js的執行,以上代碼先執行"before css",然后開始下載三個css文件,文件下載完成,執行"after css"

3.css的下載不會阻塞后面js的下載,但是js下載完成后,被阻塞執行

將"after css"那段代碼注釋掉,發生了巨大的改變

可以看到domcontentloaded事件觸發了,但是此時頁面是空白的

這是因為雖然js會阻止dom的解析,但是css不會阻止dom的解析。

第一個案例中,遇到"before css"代碼,dom被阻塞,執行js代碼,執行完之后繼續解析,遇到link標簽后,開始下載css文件,dom解析繼續,遇到script標簽,dom解析被阻塞,且js代碼不會被執行,等到css文件下載並且解析完成后,js代碼開始執行,執行完之后,繼續dom解析,最后生成dom樹,拋出domcontentloaded事件,然后瀏覽器開始渲染頁面,出現頁面元素。

第二個案例中,沒有"after css"代碼,那么在下載css文件的時候,dom解析沒有被阻塞,那么當設置下載網速20kb時,css還沒下載解析完成,dom解析就已經完成了。此時拋出domcontentloaded事件,但是瀏覽器此時還不能渲染元素,因為元素的渲染除了dom樹外還需要cssdom配合,確定元素的大小位置。這樣就導致了css文件沒有下載解析完成時,dom解析完成了,但是渲染被阻塞着。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM