JS加载时机

在 JavaScript 中,脚本的加载时机直接影响页面的解析、渲染和交互体验。默认情况下,浏览器解析 HTML 时遇到<script>标签会暂停 HTML 解析,优先下载并执行脚本(因为脚本可能会修改 DOM/CSSOM),这可能导致页面加载阻塞。

我们可以通过多种方式控制 JS 的加载时机,以优化页面性能和交互体验,具体如下:

一、默认加载行为(同步加载)

最基础的<script>标签加载方式,会阻塞 HTML 解析:

js 复制代码
<!-- 同步加载:遇到脚本会暂停HTML解析,下载并执行完成后才继续解析 -->
<script src="script.js"></script>

特点

  • 阻塞 HTML 解析和页面渲染,可能导致页面空白时间过长。
  • 脚本执行时可以访问到当前已解析的 DOM(但后续 DOM 还未解析)。

二、控制加载时机的常用方式

1. 利用async属性(异步加载 + 乱序执行)

async让脚本异步下载 (不阻塞 HTML 解析),下载完成后立即执行(可能打断 HTML 解析):

js 复制代码
<!-- 异步加载:下载时不阻塞解析,下载完立即执行(执行顺序不确定) -->
<script src="script1.js" async></script>
<script src="script2.js" async></script>

特点

  • 脚本下载和 HTML 解析并行进行,不阻塞初始解析。
  • 执行顺序与下载完成时间有关(谁先下载完谁先执行),适合无依赖的独立脚本(如广告、统计代码)。
  • 执行时可能 DOM 尚未完全解析,需注意访问 DOM 的时机。

2. 利用defer属性(异步加载 + 顺序执行)

defer让脚本异步下载 (不阻塞 HTML 解析),但延迟到 HTML 完全解析后执行,且保持脚本顺序:

js 复制代码
<!-- 延迟执行:下载不阻塞解析,等HTML解析完后按顺序执行 -->
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>

特点

  • 下载和 HTML 解析并行,不阻塞初始解析。
  • 执行顺序与标签在 HTML 中的顺序一致(先 script1 后 script2),适合有依赖关系的脚本(如库 + 业务代码)。
  • 执行时机在DOMContentLoaded事件之前(DOM 完全就绪后)。

3. 动态创建<script>标签(按需异步加载)

通过 JavaScript 动态创建<script>元素并插入 DOM,默认是异步加载(不阻塞解析)

html 复制代码
<script>
  // 动态创建脚本,按需加载
  function loadScript(url, callback) {
    const script = document.createElement('script');
    script.src = url;
    // 加载完成后执行回调
    script.onload = callback;
    // 插入到DOM中开始下载
    document.body.appendChild(script);
  }

  // 用法:需要时再加载
  loadScript('library.js', () => {
    console.log('库加载完成,可以使用了');
  });
</script>

特点

  • 完全由代码控制加载时机(如用户触发事件后加载),实现 "按需加载"。
  • 默认异步加载,不阻塞 HTML 解析。
  • 可通过script.async = false强制按插入顺序执行(类似 defer)。

4. 模块加载(type="module"

ES6 模块默认采用延迟加载(类似defer),且支持模块化语法(import/export):

js 复制代码
<!-- 模块加载:默认延迟执行,按顺序加载,支持import -->
<script type="module" src="module1.js"></script>
<script type="module" src="module2.js"></script>

特点

  • 下载时不阻塞 HTML 解析,执行时机在 HTML 解析完成后(类似 defer)。
  • 按标签顺序执行,支持模块间依赖(通过import声明)。
  • 模块内部默认是严格模式(use strict),且顶层变量不污染全局。
  • 可通过async属性让模块下载完成后立即执行(忽略顺序)
js 复制代码
<script type="module" src="module.js" async></script>

5. 放置位置控制(传统方式)

将脚本放在<body>底部,让 HTML 解析完成后再加载执行:

js 复制代码
<body>
  <!-- 页面内容 -->
  <div>...</div>
  
  <!-- 放在body底部:HTML解析完后再加载执行 -->
  <script src="script.js"></script>
</body>

特点

  • 避免阻塞 HTML 解析,脚本执行时可访问完整 DOM。
  • 但下载仍会阻塞页面渲染(不如 async/defer 高效),现代开发更推荐前几种方式。

6. 监听页面就绪事件(控制执行时机)

即使脚本提前加载,也可通过事件监听延迟执行(确保 DOM 就绪):

js 复制代码
<script src="script.js"></script>
<script>
  // 等待DOM完全解析完成后执行
  document.addEventListener('DOMContentLoaded', () => {
    // 此时可安全操作DOM
    initApp();
  });

  // 等待所有资源(图片、样式等)加载完成后执行
  window.addEventListener('load', () => {
    // 适合需要依赖资源的操作
    startAnimation();
  });
</script>

三、总结:不同场景的选择

需求场景 推荐方式 核心优势
脚本无依赖,独立运行 async 属性 下载最快,不阻塞解析
脚本有依赖,需按顺序执行 defer 属性 顺序执行,DOM 就绪后执行
按需加载(如用户操作后) 动态创建<script>标签 灵活控制加载时机
现代模块化项目 type="module" 支持 import/export,延迟执行
兼容旧环境,确保 DOM 访问 放在<body>底部 简单直接,兼容性好

合理控制 JS 加载时机,可以显著减少页面阻塞时间,提升用户体验(尤其是首屏加载速度)。

相关推荐
itslife2 小时前
从头看 vite 源码 - 调试
前端
叫我詹躲躲2 小时前
ES2025:10个让你眼前一亮的JavaScript新特性
前端·javascript
乐~~~2 小时前
解决avue-input-tree组件重置数据不回显/重置失败
前端·javascript·vue.js
你的电影很有趣3 小时前
lesson68:JavaScript 操作 HTML 元素、属性与样式全指南
开发语言·前端·javascript
妄小闲3 小时前
html网站源码 html网页模板下载
前端·html
小二·3 小时前
前端笔记:HTML output标签介绍及用法
javascript·笔记·html5
宁雨桥3 小时前
前端登录加密实战:从原理到落地,守护用户密码安全
前端·安全·状态模式
椒盐螺丝钉3 小时前
TypeScript类型兼容性
运维·前端·typescript