defer和async属性在script标签中的使用

浏览器在解析HTML的过程中,遇到了script元素时的默认行为:

  • 暂停HTML解析,下载JavaScript代码,并且执行JavaScript的脚本;
  • 等到JavaScript脚本执行结束后,才会继续解析HTML,构建DOM树;

原因:

  • JavaScript的作用之一就是操作DOM,并且可以修改DOM;
  • 如果我们等到DOM树构建完成并且渲染再执行JavaScript,会造成严重的回流和重绘,影响页面的性能;
  • 所以在遇到JavaScript元素时,有限下载和执行JavaScript代码,再继续构建DOM树;

但是在目前的开发模式中(如:Vue、React)中,脚本往往比HTML页面更”重“,需要的处理时间也就更长,因此也带来了新的问题:

  • 阻塞渲染:脚本执行期间,浏览器会暂停HTML解析,同时阻塞页面渲染,如果脚本执行时间较长(如加载外部资源、执行复杂计算),会导致页面长时间处于白屏或卡顿状态;
  • 依赖关系处理:如果脚本依赖外部资源(如API数据、其他脚本),当外部资源加载较慢时,会进一步延长脚本执行时间,加剧阻塞问题;

为了解决这个问题,script元素提供了两个属性:defer 和 async。

defer (延迟执行):

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

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

  • 如果脚本提前下载好了,它会等待DOM Tree构建完成,在DOMContentLoaded事件之前先执⾏defer中的代码,所以DOMContentLoaded总是等待defer中的代码先执行完成;

    // js/defer-demo.js
    console.log("defer-demo execute~");
    
    <!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在script标签中的使用</title>
        <script defer src="./js/defer-demo.js"></script>
        <script>
          window.addEventListener("DOMContentLoaded", () => {
            console.log("DOMContentLoaded");
          });
        </script>
      </head>
      <body>
        <ul>
          <li>li</li>
          <li>li</li>
          <li>li</li>
        </ul>
      </body>
    </html>
    

    请添加图片描述

  • 顺序执行:多个带defer的脚本会保持正确的顺序执行。

    <script defer src="./js/defer-demo1.js"></script>
    <script defer src="./js/defer-demo2.js"></script>
    <script>
      window.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
      });
    </script>
    

    在这里插入图片描述

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

async的作用:

async 特性与 defer 有些相似,也能够让脚本不阻塞页面;
async 脚本的下载和执行都是独立的:

  • 异步加载并执行:async脚本会在下载和立即执行,不能保证在DOMContentLoaded之前或者之后执⾏(执⾏时会阻塞DOM Tree的构建);

    // js/async-demo.js
    console.log("async-demo execute~");
    
    <!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>async在script标签中的使用</title>
        <script async src="./js/async-demo.js"></script>
        <script>
          window.addEventListener("DOMContentLoaded", () => {
            console.log("DOMContentLoaded");
          });
        </script>
      </head>
      <body>
        <ul>
          <li>li</li>
          <li>li</li>
          <li>li</li>
        </ul>
      </body>
    </html>
    
    DOMContentLoaded
    async-demo execute~
    
    <!-- 模拟需要渲染的HTML内容较多时 -->
    <script>
      let count = 0;
      for (let i = 0; i < 10000; i++) {
        count += 1;
      }
      console.log(count);
    </script>
    
    10000
    async-demo execute~
    DOMContentLoaded
    
  • 不保证顺序:多个async脚本的是独立下载、独立运行的,不会等待其他脚本。

总结:

<script> 标签的 deferasync 属性⽤来控制外部脚本⽂件的加载和执⾏⽅式,它们对于改善⻚⾯加载速度⾮常有帮助。

属性加载是否阻塞HTML解析执行时机执行顺序适用场景
defer否(异步)DOM Tree构建完成后,DOMContentLoaded事件之前执行严格按文档顺序需要等待DOM就绪的脚本(如操作DOM的库或代码)。
脚本之间依赖性强,需按顺序执行。
async否(异步)下载完立即执行不确定(谁先下载完谁先执行)独立脚本(如分析工具、广告、统计代码);
脚本之间没有依赖关系,或依赖关系可通过其他方式处理。

图示流程:

HTML解析
├─ 遇到普通<script> → 暂停解析,下载并执行 → 继续解析
├─ 遇到async脚本 → 后台下载,下载完立即执行(可能中断解析)
└─ 遇到defer脚本 → 后台下载,HTML解析完后再按顺序执行

在现代化框架开发过程中,往往不需要我们⾃⼰来配置async或者defer,在使⽤脚⼿架或者⾃⼰搭建的webapck或 者vite项⽬进⾏打包时,它会根据需要帮我们加上defer属性,某些情况下我们想要进⾏性能优化时,也可以⼿动的 加上async属性(例如⼀些第三⽅的分析⼯具或者⼴告追踪脚本)。

注意事项:

  • 如果同时使用asyncdefer,现代浏览器会优先采用async
  • 内联脚本(无src)的asyncdefer会被忽略。
### HTML `script` 标签中 `defer` 与 `async` 属性的区别及用法 #### 基本定义 在 JavaScript 中,`<script>` 标签用于引入外部 JavaScript 文件或包含嵌入式 JavaScript 代码。为了优化页面加载性能并控制脚本的执行时机,HTML 提供了两种特殊的属性——`defer` `async`。 #### `defer` 属性 当 `<script>` 标签带有 `defer` 属性时,浏览器会继续解析渲染后续的文档内容,而不会阻塞 DOM 构建过程[^3]。脚本会在整个 HTML 文档解析完成后立即按顺序执行,这意味着如果页面中有多个带有 `defer` 属性的脚本,则它们会按照其在 HTML 中出现的顺序依次执行[^5]。 以下是使用 `defer` 的示例代码: ```html <script src="script1.js" defer></script> <script src="script2.js" defer></script> ``` #### `async` 属性 对于具有 `async` 属性的 `<script>` 标签,浏览器同样不会因为下载该脚本文件而停止解析其他部分的内容[^4]。然而,一旦脚本被成功获取回来之后就会立刻被执行,而不等待任何其他的资源或者操作完成。需要注意的是,这种行为可能导致不同脚本之间的执行次序变得不可预测,具体取决于各个请求返回的速度差异。 下面是一个简单的例子展示了如何应用 `async` 属性: ```html <script src="library.js" async></script> <script src="app.js"></script> <!-- 这里可能还未等到 library.js 加载完毕 --> ``` #### 主要对比 | 特性 | `normal (no attribute)` | `defer` | `async` | |-----------------|-------------------------------|----------------------------------|---------------------------------| | **下载阶段** | 阻挡DOM构建 | 并行下载 | 并行下载 | | **执行阶段** | 按照遇到的位置暂停解析直到运行完当前脚本 | 在DOMContentLoaded事件触发前依序执行 | 尽早尽可能快地单独执行 | 总结来说,在实际开发过程中可以根据需求选择合适的策略来提升网页的整体表现力。例如,如果你希望某些非关键性的第三方统计工具尽快发挥作用但又不影响主要业务逻辑的话,那么就可以采用 `async` 方式;而对于那些需要确保特定先后关系才能正常运作的部分则更适合运用 `defer` 技术[^1]。 ### 结论 通过上述分析可以看出,虽然两者都能实现异步加载效果从而改善首屏呈现速度等方面的优势,但是由于各自独特的特性决定了适用场景也有所不同。因此理解清楚两者的本质区别是非常重要的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值