script在HTML中的加载

在访问网页的时候,浏览器是如何渲染当前的页面的?浏览器获取到输入(HTMLCSSJavascript)的时候,输入中有DOM Tree的信息,有样式信息,Javascript代码,在加载页面的时候,如果DOM的加载和解析太耗时的话,首屏的大量留白是很影响用户体验的,这里我们来看看,对于Scritp在页面中的加载对HTML的加载的性能影响。

Script在页面中的位置

script标签在页面中放置的位置对于整个页面的加载和功能影响是很大,默认情况下,浏览器在解析HTMl页面的时候,遇到script标签,如果是inline的话,会立即执行script标签中的代码,这里就牵扯一个问题,浏览器对HTML的解析是按照HTML内容的输入顺序执行的,如果scripthead中,如果需要操作DOM,这个时候浏览器对文档的解析并没有结束,可能这时候需要的DOM并不存在。

<html>
  <head>
    <link rel="stylesheet" href="./style.css"></link>
    <script type="text/javascript">
      function foo() {
        console.log('Dom Length: ', document.getElementsByClassName('test').length);
      }
      foo();
    </script>
  </head>
  <body>
    <div class="test">Test1</div>
    <div class="test">Test2</div>
    <div class="test">Test3</div>
    <div class="test">Test4</div>
    <div class="test">Test5</div>
  </body>
</html>

如上,是一个inlinescript,在script的代码中去取DOM,这个时候浏览器在执行这段代码的时候,浏览器并没有解析到对应的DOM

inline in head

所以,为了避免这种情况,一般来说,会将script标签放置在</body>标签前,这个时候,所有的DOM都解析好了,不存在找不到DOM的情况。

<html>
  <head>
    <link rel="stylesheet" href="./style.css"></link>
  </head>
  <body>
    <div class="test">Test1</div>
    <div class="test">Test2</div>
    <div class="test">Test3</div>
    <div class="test">Test4</div>
    <div class="test">Test5</div>
    <script type="text/javascript">
      function foo() {
        console.log('Dom Length: ', document.getElementsByClassName('test').length);
      }
      foo();
    </script>    
  </body>
</html>

inline in body

外部Script的加载对HTML解析的影响

在加载外部的script的时候,script的加载对于页面的性能影响更加明显,加载外部脚本的时候牵扯发外部的请求去请求脚本,这个时候HTML的解析是被阻塞住的,外部script加载成功后,马上执行script脚本,执行完后,才继续解析剩下的HTML

<html>
  <head>
    <link rel="stylesheet" href="./style.css"></link>
  </head>
  <body>
    <div class="test">Test1</div>
    <div class="test">Test2</div>
    <div class="test">Test3</div>
    <div class="test">Test4</div>
    <div class="test">Test5</div>
    <script type="text/javascript" src="./app.js"></script>
  </body>
</html>

extend script in body 从上图的profile的图我们可以发现,浏览器在解析HTML的时候,默认在解析到script标签的时候,会加载对应的Javascript代码,加载完成后立即执行,这个过程会将HTML解析阻塞住,那有没有办法解决页面阻塞的问题呢?在HTML5中引入了asyncdefer这两个属性,可以解决script加载阻塞页面解析的问题。

Script中的Async和Defer属性

首先我们看看MDN对这两个属性的解释: extend async mdn 如上,是async属性对script标签的加载和执行的影响:

  • asynctruescript是异步加载和执行的,加载过程不会阻塞浏览器对HTML的解析
  • 有多个asynctruescript的执行的顺序是不确定的,执行顺序并不是脚本的引用顺序
<html>
  <head>
    <link rel="stylesheet" href="./style.css"></link>
  </head>
  <body>
    <div class="test">Test1</div>
    <div class="test">Test2</div>
    <div class="test">Test3</div>
    <div class="test">Test4</div>
    <div class="test">Test5</div>
    <script type="text/javascript" async=true src="./app.js"></script>
  </body>
</html>

从上面的profile的图可以很明显的看到这个过程,Javascript的加载和HTML的解析是并行的,并不会阻塞HTMLparse,但是javascript代码的执行的实际这里是无法确定的。

<html>
  <head>
    <link rel="stylesheet" href="./style.css"></link>
  </head>
  <body>
    <div class="test">Test1</div>
    <div class="test">Test2</div>
    <div class="test">Test3</div>
    <div class="test">Test4</div>
    <div class="test">Test5</div>
    <script type="text/javascript" async=true src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
    <script type="text/javascript" async=true src="./app.js"></script>
  </body>
</html>

如上,我们在这里从cdn上引入了JQuery的代码,我们来看看这里async=true的时候,对javascript代码的执行情况的影响。 async jquery 在上面的profile的图中,我们可以很明显的看到,javascript代码的执行顺序,和script的引入顺序并不一致。 我们接着来看看defer属性对script标签的影响: extend defer mdn 根据MDN的解释,在defer属性为true的情况下,script标签会异步加载,脚本的执行是按照script标签在HTML中出现顺序执行,等所有的script执行完毕之后才会触发DOMContentLoaded事件。 对于script的加载和执行,结合文中实际的例子,我们总结一下:

  • script标签默认是同步加载,加载完成之后立即执行
  • asynctrue的时候,script的加载是异步的(不会阻塞HTML的解析过程),代码的执行顺序不一定是script标签出现的顺序。
  • defer的加载和async是异步的不会阻塞HTMLparse,和async区别在于,defer标签的执行是按照在文档中定义的顺序执行的,在代码执行完毕之后才触发DOMContentLoadedasync的代码的执行并不对DOMContentLoaded有影响(代码的执行可能在DOMContentLoaded事件之后,意味着如果在asynctrue的脚本中拿不到DOMContentLoaded的事件)]

Reference


兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub