浏览器运行原理—script元素和页面解析的关系

118 阅读2分钟

浏览器运行原理—script元素和页面解析的关系

  • 浏览器在解析HTML的过程中,遇到了script元素是不能继续构建DOM树的;

  • 它会停止继续构建,首先下载JavaScript代码,并且执行JavaScript的脚本;

  • 只有等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树;

index.html 如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试代码</title>
  </head>
  <body>
    <div>我是div元素</div>
    <p>我是段落</p>
    <script>
      debugger;
      console.log("test");
    </script>
    <div>我是js加载完才显示的元素</div>
  </body>
</html>

index.js 如下:

debugger
console.log("test")

上面的代码中,我们使用了debugger使js的执行停住了,所以

为什么要这样做呢?

这是因为JavaScript的作用之一就是操作DOM,并且可以修改DOM

如果我们等到DOM树构建完成并且渲染再执行JavaScript,会造成严重的回流和重绘,影响页面的性能;

所以会在遇到script元素时,优先下载和执行JavaScript代码,再继续构建DOM树;

但是这个也往往会带来新的问题,特别是现代页面开发中:

在目前的开发模式中(比如Vue、React),脚本往往比HTML页面更“重”,处理时间需要更长;

所以会造成页面的解析阻塞,在脚本下载、执行完成之前,用户在界面上什么都看不到;

为了解决这个问题,script元素给我们提供了两个属性(attribute):defer和async

defer

defer 属性告诉浏览器不要等待脚本下载(异步下载),而继续解析HTML,构建DOM Tree。即:

  • 脚本会由浏览器来进行下载,但是不会阻塞DOM Tree的构建过程;

  • 如果脚本提前下载好了,它会等待DOM Tree构建完成,在DOMContentLoaded事件之前先执行defer中的代码;(也就是说当执行defer文件中的js代码时,DOM Tree肯定已经构建完成了(因此我们可以在defer文件中进行DOM操作),且defer文件中的js代码执行完后才会触发DOMContentLoaded事件。

<!-- index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>defer</title>
</head>
<body>
    <div>我是div元素</div>
    <p>我是段落</p>
    <script>
        window.addEventListener("DOMContentLoaded", () => {
            console.log("DOMContentLoaded")
        })
    </script>
    <script src="./index.js" defer></script>
    <div>我是js加载完才显示的元素</div>
</body>
</html>
// index.js
debugger
console.log("test")

运行index.html,控制台输出结果为:

image.png

  • **另外多个带defer的脚本是可以保持正确的顺序执行的。**即按出现的顺序执行每个带defer的脚本。

  • 从某种角度来说,defer可以提高页面的性能,并且推荐放到head元素中;

  • 注意:defer仅适用于外部脚本,对于script默认内容会被忽略。

async

async 特性与 defer 有些类似,它也能够让脚本不阻塞页面。

async是让一个脚本完全独立的:

  • 浏览器不会因 async 脚本而阻塞(与 defer 类似);

  • async脚本不能保证顺序,它是独立下载、独立运行,不会等待其他脚本;

  • async不会能保证在DOMContentLoaded之前或者之后执行;

使用场景:

defer通常用于需要在文档解析后操作DOM的JavaScript代码,并且对多个script文件有顺序要求的;

async通常用于独立的脚本,对其他脚本,甚至DOM没有依赖的;

总结defer和async的区别:

使用defer属性的JavaScript会按照文件顺序执行,而使用async属性的JavaScript不会

使得defer属性的JavaScript会在监听到DOMContentLoaded事件时执行代码,而async不能保证在DOMContentLoaded之前或之后执行

本文使用 文章同步助手 同步