Skip to content

优化 script 标签加载缓慢问题

要点速览

  • 脚本阻塞本质:默认 script 会暂停 HTML 解析与渲染。
  • 选型根原则:按“是否依赖 DOM/其他脚本”选择 asyncdefer
  • async 适合独立脚本;defer 适合大多数业务脚本(有依赖)。
  • type="module" 天生“延迟执行(类 defer)”,按顺序执行且支持依赖图。

快速上手

用一个最小页面观察 asyncdefer 与默认脚本的加载与执行时机:

html
<!DOCTYPE html>
<html>
  <head>
    <!-- 独立脚本:下载并行,谁先下完谁先执行(会打断解析) -->
    <script src="/js/a.js" async></script>
    <!-- 业务脚本:下载并行,等解析完成后按书写顺序执行 -->
    <script src="/js/b.js" defer></script>
    <!-- 默认脚本:遇到即阻塞解析与渲染,下载并执行后才继续 -->
    <script>
      console.log("inline start - 阻塞解析");
    </script>
  </head>
  <body>
    <h1 id="title">Hello</h1>
    <script>
      document.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
      });
      window.addEventListener("load", () => {
        console.log("window.load");
      });
    </script>
  </body>
</html>

性能认知与前提

  • 默认 script 会阻塞 HTML 解析,影响首屏;async/defer 能并行下载减轻阻塞。
  • async 在下载完成会立即执行,可能打断解析,且不保证脚本间顺序;defer 保序、等解析完成再执行。
  • 业务脚本如果依赖 DOM 或其他库,优先 defer;统计/广告等独立脚本使用 async

二、面试回答误区与核心原则

(一)常见误区

多数人回答“脚本优化”类问题时,仅罗列asyncdefer”等技术名词,缺乏对问题本质的分析,无法体现系统性思维,难以满足面试官对“解决问题能力”的考察需求。

(二)高分核心原则

面对性能优化问题,先定义问题根源,再针对性给出解决方案,而非直接堆砌技术方案。该原则可体现从“诊断”到“解决”的完整思路,证明不仅会用工具,更能从根源解决问题。

三、script 标签加载缓慢的问题根源

浏览器默认处理script标签的机制是阻塞渲染

  1. 当浏览器解析 HTML 遇到script标签时,会立即暂停 HTML 解析、CSS 渲染等所有工作;
  2. 优先下载script脚本文件,下载完成后立即执行脚本;
  3. 脚本执行结束后,才恢复 HTML 解析和页面渲染。

这一机制会导致页面出现“白屏”或“加载停滞”,是脚本引发页面加载缓慢的核心原因(注:字幕中“squid 标签”应为笔误,实际指script标签)。

拓展:模块脚本(type="module")的默认行为

  • type="module" 脚本默认“延迟执行”(不阻塞解析,等 HTML 解析完成后执行),且脚本之间按依赖图与书写顺序执行。
  • 旧浏览器可结合 nomodule 提供降级:现代浏览器加载模块脚本,旧浏览器加载非模块脚本。

四、优化方案:基于脚本依赖关系选择策略

优化script标签加载的唯一判断标准是:脚本是否依赖 DOM 或其他脚本。需根据依赖情况,选择不同属性进行优化,具体对比如下:

优化属性适用场景核心特性注意事项
async脚本完全独立(不依赖 DOM、不依赖其他脚本),如:
- 广告脚本
- 统计分析脚本(如埋点脚本)
1. 下载阶段:与 HTML 解析并行进行,不阻塞渲染;
2. 执行阶段:脚本下载完成后立即执行(会暂停当前 HTML 解析);
3. 执行顺序:不保证脚本间的执行顺序(谁先下载完谁先执行)
滥用会导致执行顺序混乱,引发偶发性 bug(如 A 脚本依赖 B 脚本,但 A 先执行),仅适用于无依赖的独立脚本
defer绝大多数业务脚本(依赖 DOM、依赖其他脚本),如:
- 页面交互逻辑脚本
- 依赖 jQuery 的业务脚本
1. 下载阶段:与 HTML 解析并行进行,不阻塞渲染;
2. 执行阶段:脚本下载完成后,等待 HTML 解析完全结束再执行;
3. 执行顺序:严格按照脚本在 HTML 中的书写顺序执行
兼顾“不阻塞渲染”和“保证依赖关系”,是业务脚本的最优选择,无明显使用风险

场景选型建议

  • 统计/广告/监控等独立脚本 → async(并行下载,尽快上报,不依赖 DOM)。
  • 应用主包、依赖 DOM 与第三方库 → defer(并行下载,保序,解析后执行)。
  • 现代前端入口(ESM) → type="module"(天然延迟,支持 import 依赖图)。

五、面试回答框架(示例)

当面试官问“页面加载慢,脚本是主要原因,该怎么优化?”时,可按以下框架回答,体现完整思路:

  1. 先定位问题根源
    “首先,脚本导致页面加载慢的核心是浏览器默认处理script标签的阻塞机制——遇到script会暂停 HTML 解析和渲染,优先下载执行脚本,导致页面白屏或停滞。”

  2. 再说明判断标准与优化策略
    “优化的关键是看脚本是否有依赖:

  • 如果脚本完全独立(如广告、统计脚本),用async属性,让脚本并行下载、下载完立即执行,不阻塞初始渲染;
  • 如果脚本依赖 DOM 或其他脚本(如业务逻辑脚本),用defer属性,既保证脚本并行下载不阻塞渲染,又能在 HTML 解析完后按顺序执行,确保依赖关系正确。”
  1. 最后总结思路
    “核心是‘先看依赖,再定策略’,避免盲目使用属性导致新问题,从根源解决脚本阻塞渲染的问题。”

六、实践建议

  1. 业务脚本优先用defer:多数业务脚本存在依赖(如依赖 DOM 加载完成、依赖其他库),defer能兼顾性能与稳定性,是更安全的选择;
  2. 独立脚本谨慎用async:仅对无任何依赖的脚本使用async,并在代码注释中明确标注“无依赖,可 async 加载”,避免后续维护时误改引发 bug;
  3. 避免默认script标签:除特殊场景(如脚本必须在页面头部执行且无替代方案),尽量不使用无属性的默认script标签,减少渲染阻塞。

常见误区

  • 误把 async 用在有依赖的业务脚本,导致执行顺序混乱与偶发现象。
  • 假设 defer 能解决所有问题,却忽略脚本自身的网络瓶颈(DNS、TLS、连接复用)。
  • 将脚本“堆到页面底部”替代 defer,虽然减少阻塞,但无法保证脚本顺序与依赖就绪。

进一步优化(按需选用)

  • 对第三方域名使用 dns-prefetchpreconnect(减少 DNS 与握手延迟):
    html
    <link rel="dns-prefetch" href="//cdn.example.com" />
    <link rel="preconnect" href="https://cdn.example.com" crossorigin />
  • 关键脚本可结合 preload(提前调度下载,但不会自动执行):
    html
    <link rel="preload" as="script" href="/js/app.js" />
    <script src="/js/app.js" defer></script>
  • 长期建议:整合打包与分包策略(路由/功能拆分),减少初始脚本体积与阻塞面。

小结与后续

  • 先诊断“是否阻塞解析”与“是否存在依赖”,再选择 async/defer/module
  • 业务脚本优先 defer,独立脚本用 async;ESM 入口用 type="module"
  • 配合网络层优化(dns-prefetch/preconnect/preload)与打包拆分,进一步降低首屏压力。