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解析完成了,但是渲染被阻塞着。

 

posted @ 2018-07-25 11:27  Tinypan  阅读(10834)  评论(3编辑  收藏  举报