性能优化之脚本加载方式async、defer

176 阅读3分钟

script脚本加载方式对比

//默认引入
<script src="analytics.js"></script>

//defer引入
<script defer src="analytics.js"></script>

//async引入
<script async src="analytics.js"></script>
特性默认scriptasyncdefer
加载方式同步加载,阻塞 HTML 解析异步加载,与 HTML 解析并行异步加载,与 HTML 解析并行
执行时机脚本下载后立即执行,阻塞 HTML 解析脚本下载完成后立即执行(可能中断 HTML 解析,可能中断的原因下面会解释)等待 HTML 解析完成后执行
执行顺序按脚本在 HTML 中出现的顺序执行执行顺序不确定(先下载完的先执行)按脚本在 HTML 中出现的顺序执行

为什么async可能中断 HTML 解析?

JavaScript 是 单线程 的,且浏览器的 JavaScript 引擎线程渲染线程 是互斥的。当 JavaScript 执行时,渲染线程必须暂停,以确保操作 DOM 的一致性。

  • 执行脚本时的线程互斥
    当脚本下载完成后,浏览器必须切换到 JavaScript 引擎线程执行脚本。由于 JavaScript 可能操作 DOM 或修改样式,必须暂停 HTML 解析和渲染,以避免数据不一致(例如,脚本修改了某个元素的属性,而渲染线程正在使用旧数据)。
  • 抢占优先级
    如果 HTML 解析尚未完成,但脚本已下载完毕,浏览器会优先执行脚本(即使 HTML 还未解析完),因为脚本的执行可能对页面功能至关重要(如初始化逻辑)。

核心区别

  • async
    • 加载:异步下载脚本,与 HTML 解析并行。

    • 执行:脚本下载完成后 立即执行可能中断 HTML 解析

    • 执行顺序无序(谁先下载完谁先执行)。

    • 适用场景

      • 独立脚本(如第三方统计代码、广告 SDK)。
      • 不依赖 DOM 或其他脚本。
  • defer
    • 加载:异步下载脚本,与 HTML 解析并行。

    • 执行:等待 HTML 解析完成 后执行(在 DOMContentLoaded 事件之前)。

    • 执行顺序有序(按脚本在 HTML 中出现的顺序执行)。

    • 适用场景

      • 依赖 DOM 或其他脚本的脚本(如页面初始化逻辑)。
      • 需要按顺序执行的脚本(如 jQuery + 插件)。

对 DOM 的影响

  • async:脚本可能在 DOM 解析完成前执行,因此 不保证 DOM 已加载
  • defer:脚本在 DOM 解析完成后执行,可以 安全访问完整的 DOM

性能优化建议

  • 优先使用 defer
    如果脚本需要操作 DOM 或依赖其他脚本,使用 defer 可以避免阻塞页面渲染。
  • 使用 async
    对于完全独立的脚本(如统计代码),使用 async 可以让脚本尽快执行,无需等待 DOM 解析。
  • 避免混合使用
    同时使用 defer 和 async 时,现代浏览器会优先采用 async 行为(忽略 defer)。

总结

特性asyncdefer
加载方式异步加载异步加载
执行时机下载完成后立即执行HTML 解析完成后执行
执行顺序无序有序(按 HTML 中的顺序)
DOM 访问不保证 DOM 已加载可安全访问完整 DOM
适用场景独立脚本(如统计代码)依赖 DOM 或其他脚本