LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

HTML script 标签指南:位置、加载策略与性能优化

admin
2026年5月13日 14:34 本文热度 41

在现代前端开发中,<script> 标签是连接 HTML 结构与 JavaScript 行为的核心通道。然而,它的放置位置、加载方式以及执行时机,对页面渲染性能、用户交互体验乃至代码可维护性都有着深远影响。许多开发者习惯于“把脚本放在页面底部”,但如今 async、defer、ES 模块以及更精细的资源加载策略已提供了更优解。

本文将从基础到进阶,系统梳理 <script> 元素的最佳实践,帮助你构建更快、更可靠的网页。

一、基础回顾:内联脚本与外部脚本


<script> 元素有两种基本使用形式:

  • 内联脚本:直接嵌入 JavaScript 代码,适合页面级别的小逻辑或初始数据。

  • 外部脚本:通过 src 属性引用独立的 .js 文件,推荐用于可缓存、可复用的逻辑。

示例:

<!-- 内联脚本 --><script>  window.APP_CONFIG = { apiUrl'/api' };</script>
<!-- 外联脚本 --><script src="/js/main.js"></script>

注意:当 src 被指定时,标签内的任何脚本内容都会被忽略。


二、脚本位置如何影响渲染与 DOM 访问


1. 放置在 <head> 中(传统但需谨慎)

<!DOCTYPE html><html><head>  <script src="critical.js"></script></head><body>...</body></html>

  • 阻塞渲染:浏览器必须下载并执行完脚本,才能继续解析 <body> 内容。

  • 无法直接访问 DOM:此时 <body> 尚未解析,操作 DOM 元素会返回 null。

  • 适用场景:必须在页面任何内容展示前运行的脚本,例如 polyfill、document.write、性能打点(需尽早)、配置对象。


2. 放置在 <body> 末尾(传统最佳实践)

<body>  <!-- 所有页面内容 -->  <script src="app.js"></script></body>

  • 不阻塞渲染:用户先看到页面内容,再获取并执行脚本。

  • 可安全访问 DOM:脚本执行时,所有元素均已解析完成。

  • 局限:脚本下载和执行的开始时间被推迟,如果脚本较大且网络慢,可能影响后续交互的响应速度。

为了解决“头部阻塞”与“底部延迟”之间的矛盾,HTML5 引入了 async 和 defer。

三、异步加载的核心武器:async 与 defer


两者都让浏览器在 不阻塞 HTML 解析 的前提下下载脚本。主要区别在于 执行时机 和 顺序保证 。

特性

普通脚本(头部)

async

defer

下载时机

遇到立即下载,阻塞解析

立即异步下载

立即异步下载

执行时机

下载后立即执行,阻塞解析

下载完成后立即执行(可能阻塞解析)

HTML 解析完成后、DOMContentLoaded 前执行

执行顺序

按照文档顺序

不保证顺序(谁先下载完谁执行)

保证顺序

DOM 可访问

否(位于头部时)

不确定(取决于执行时 DOM 是否解析完)

执行时序示意

普通脚本:┌───────────────────────────────────────────────────────────┐│ HTML解析 → 遇到<script> → 停止解析 → 下载 → 执行 → 继续解析 │└───────────────────────────────────────────────────────────┘async:┌───────────────────────────────────────────────────────────┐│ HTML解析 → 遇到<script> → 并行下载 → 下载完成 → 暂停解析   ││                                  → 执行脚本 → 继续解析   │└───────────────────────────────────────────────────────────┘defer:┌───────────────────────────────────────────────────────────┐│ HTML解析 → 遇到<script> → 并行下载 → 解析完成 → 执行脚本   │└───────────────────────────────────────────────────────────┘

代码示例

<!-- 顺序不确定,适合完全独立的第三方脚本(统计、广告) --><script async src="https://www.google-analytics.com/analytics.js"></script>
<!-- 保证顺序,适合依赖 DOM 且相互依赖的脚本 --><script defer src="jquery.js"></script><script defer src="plugin.js"></script><script defer src="app.js"></script>

现代实践建议

  • 默认使用 defer:既能尽早下载,又不阻塞渲染,还能保证执行顺序和 DOM 就绪,是多数业务脚本的“安全牌”。

  • 仅用 async:当脚本完全自包含,不依赖其他脚本、也不被其他脚本依赖时(如埋点、A/B 测试)。

四、ES 模块:type="module" 与 nomodule

现代浏览器原生支持 ES 模块(ESM),它们默认具备 defer 的行为,并带来模块化作用域、严格模式、import/export 等特性。

<!-- 自动延迟执行,等价于 defer --><script type="module" src="main.js"></script>
<!-- 内联模块也支持导入 --><script type="module">  import { formatDate } from './utils.js';  console.log(formatDate(new Date()));</script>
<!-- 为旧浏览器提供回退 --><script nomodule src="legacy.js"></script>

关键特性

  • 默认启用严格模式,变量不会泄漏到全局。

  • 作用域隔离,每个模块拥有独立的作用域。

  • 默认支持 async 属性(加上后将改变执行顺序,不保证顺序)。

  • 模块脚本的跨域请求需遵循 CORS 策略(非同源时需配置 CORS)。

  • nomodule 属性可被支持 ESM 的浏览器忽略,用于优雅降级。


五、进阶属性与安全增强


属性

作用

示例

crossorigin

控制跨域请求是否携带凭证,常用于 CDN 脚本的错误捕获

<script src="https://cdn.com/lib.js" crossorigin="anonymous"></script>

integrity

子资源完整性(SRI),防止 CDN 资源被篡改。需配合 crossorigin

<script src="https://cdn.com/lib.js" integrity="sha384-..." crossorigin="anonymous"></script>

referrerpolicy

控制请求中 Referer 头的发送行为

<script referrerpolicy="origin" src="..."></script>

onload/onerror

监听脚本加载成功或失败

<script src="app.js" onload="init()" onerror="fallback()"></script>

完整性校验示例

<script src="https://code.jquery.com/jquery-3.7.1.min.js"        integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="        crossorigin="anonymous"></script>

SRI 要求资源支持 CORS,因此 crossorigin 不能省略。

SRI(Subresource Integrity 子资源完整性)是一种安全验证机制,用于确保浏览器加载的外部资源(如 JavaScript、CSS 文件)在传输过程中没有被篡改。

六、现代资源提示:preload 与 modulepreload

除了 async/defer,你可以在 <head> 中使用 <link rel="preload"> 来 提前加载 脚本,但不执行它,从而进一步缩短加载时间。

<!-- 预加载一个普通脚本,但未指定执行策略 --><link rel="preload" href="critical.js" as="script"><!-- 之后再用 defer 或 async 引用并执行 --><script defer src="critical.js"></script>
<!-- 针对 ES 模块的专用预加载 --><link rel="modulepreload" href="main.js"><script type="module" src="main.js"></script>

使用原则

  • 仅对 关键脚本如首屏必需的交互逻辑)使用 preload,过度使用会抢占带宽。

  • modulepreload 会同时预加载模块的依赖关系图,适用于深层依赖的模块树。


七、动态脚本加载与懒加载


通过 JavaScript 动态创建 <script> 可以实现按需加载,减少初始负载。

方式一:传统 DOM 操作

<button id="loadChart">加载图表库</button>
<script>  document.getElementById('loadChart').addEventListener('click'() => {    const script = document.createElement('script');    script.src = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.0';    script.onload = () => new Chart(...);    document.head.appendChild(script);  });</script>

方式二:动态 import()(推荐)

<script type="module">  document.getElementById('loadModule').onclick = async () => {    const module = await import('./heavy-module.js');    module.doSomething();  };</script>

动态 import() 返回 Promise,支持代码分割和按需加载,是现代构建工具(如 webpack、Vite)实现懒加载的标准方式。

八、浏览器解析机制与关键事件


理解浏览器的解析顺序有助于精确控制脚本:

  1. 预扫描(Preload Scanner:浏览器并行扫描 HTML,找到需要下载的资源(图片、CSS、脚本)并提前发起请求。

  2. 构建 DOM 树:解析 HTML 为节点,普通脚本会阻塞此过程。

  3. 构建 CSSOM 树:CSS 不阻塞 DOM 解析,但会阻塞 脚本执行 (因为脚本可能读取样式)。

  4. 执行脚本:根据 async/defer/普通规则执行。

  5. 触发事件

  • DOMContentLoaded:DOM 解析完成,defer 脚本执行完毕(不等待 async、图片、样式表等)。

  • load:所有资源(包括图片、样式、async 脚本)加载完成。

<script>  document.addEventListener('DOMContentLoaded'() => {    console.log('DOM 就绪,defer 已执行');  });  window.addEventListener('load'() => {    console.log('所有资源加载完毕');  });</script>

九、综合对比一览表


放置方式

阻塞渲染

DOM可访问性

执行顺序

推荐场景

<head> 普通

按顺序

须立即执行的 polyfill、配置

<body> 末尾

按顺序

传统兼容方案,无异步需求

<head> + async

否(执行时短暂阻塞)

不确定

不保证

独立第三方脚本(统计、广告)

<head> + defer

按顺序

现代默认选择,几乎所有业务脚本

<head> + type="module"

按顺序(默认 defer)

模块化项目(推荐)

preload + defer

按顺序

关键脚本的加载提速


总结


<script> 元素的设计虽然悠久,但在现代浏览器中已演化出一套精细的控制体系。合理利用这些机制,可以显著改善页面的首屏性能与交互流畅度。

核心建议

  1. 优先使用 <script defer> 并放在 <head> 中 —— 既利用浏览器的早期下载,又不阻塞渲染,同时保证 DOM 就绪和正确的执行顺序。这是目前最通用、最安全的实践。

  2. 第三方独立脚本(分析、广告)使用 async,防止它们影响页面主要内容。

  3. 新项目全面拥抱 ES 模块 (type="module"),享受默认延迟执行、作用域隔离和现代化语法。

  4. 对于关键的上述脚本,可配合 preload 或 modulepreload 进一步缩短加载时间。

  5. 动态加载非首屏逻辑通过动态 import() 或创建 <script> 标签),减少初始传输体积。

  6. 始终为外部 CDN 脚本配置 integrity 与 crossorigin,保障资源安全。

通过以上策略,你的网页将获得更快的可交互时间(TTI)、更流畅的渲染过程,以及更健壮的代码结构。

一句话总结:日常用 defer,独立第三方用 async,模块化用 type="module",关键资源可 preload;动态懒加载是锦上添花,安全加固不可少。

知识扩展:"defer脚本执行完毕" 的含义

"defer脚本执行完毕" 指的是:所有带有 defer 属性的脚本必须在 DOMContentLoaded 事件触发之前执行完成

1. 执行顺序图

HTML 解析开始    │    ▼遇到 <script defer src="a.js"> ──→ 开始并行下载 a.js    │    ▼遇到 <script defer src="b.js"> ──→ 开始并行下载 b.js    │    ▼HTML 解析完成    │    ▼执行 defer 脚本(按顺序): a.js → b.js    │    ▼触发 DOMContentLoaded 事件

2. 为什么 defer 脚本会在此时执行?

defer 的特性回顾

特性

说明

下载时机

立即开始下载(与 HTML 解析并行)

执行时机

等待 HTML 解析完成后

执行顺序

按在 HTML 中出现的顺序执行

关键机制

  1. 不阻塞解析:defer 脚本下载时,HTML 解析继续进行。

  2. 等待 DOM:确保脚本执行时 DOM 已完整构建。

  3. 顺序保证:多个 defer 脚本按顺序执行,保证依赖关系。


3. DOMContentLoaded 的触发条件

DOMContentLoaded 触发 = HTML解析完成 + defer脚本执行完毕

不等待的资源

触发 DOMContentLoaded 不需要等待

  • async 脚本(可能还在下载或执行)。

  • 图片(<img>)。

  • 样式表(<link rel="stylesheet">)。

  • 子框架(<iframe>)。

需要等待的资源

触发 DOMContentLoaded  必须等待

  • 所有 defer 脚本执行完毕。


4. 实际示例

<!DOCTYPE html><html><head>  <!-- 样式表:不阻塞 DOMContentLoaded -->  <link rel="stylesheet" href="style.css">  <!-- defer 脚本:会在 DOMContentLoaded 前执行 -->  <script defer src="a.js"></script>  <script defer src="b.js"></script>  <!-- async 脚本:不等待 -->  <script async src="analytics.js"></script></head><body>  <img src="large-image.jpg"> <!-- 图片:不等待 -->  <script>    document.addEventListener('DOMContentLoaded'function() {      // 此时:      // ✓ DOM 已完整解析      // ✓ a.js 和 b.js 已执行完毕      // ✗ analytics.js 可能还未执行      // ✗ large-image.jpg 可能还未加载完成      console.log('DOM ready!');    });  </script></body></html>

执行时序

时间轴 →─────────────────────────────────────────────────────────────解析HTML    ████████████████████████████████████████下载a.js       ███████████████下载b.js           ███████████████下载样式表      █████████████████下载图片              ████████████████████████下载async                 ███████████████─────────────────────────────────────────────────────────────执行a.js                                        ██执行b.js                                          ██DOMContentLoaded触发                               ▼执行async                                              ██图片加载完成                                                  ██

5. 为什么这样设计?

设计意图

  1. 保证脚本获取完整 DOM:defer 脚本执行时,DOM 已完整构建,可以安全地操作 DOM。

  2. 保证脚本执行顺序:多个 defer 脚本按顺序执行,避免依赖问题。

  3. 优化加载性能:脚本下载与 HTML 解析并行,提升首屏加载速度。

典型应用场景

<!-- 依赖 DOM 的初始化脚本 --><script defer src="app.js"></script>
<!-- 需要按顺序执行的脚本 --><script defer src="utils.js"></script><script defer src="main.js"></script>

6. 小结

资源类型

等待否

说明

defer 脚本

必须等待

执行完毕后才触发 DOMContentLoaded

async 脚本

不等待

可能在事件触发前或后执行

样式表

不等待

仅阻塞后续脚本执行

图片

不等待

不影响 DOMContentLoaded

核心要点:defer 脚本的设计目的就是在 DOM 构建完成后、DOMContentLoaded 触发前执行,确保脚本能安全地访问和操作完整的 DOM 树。


阅读原文:https://mp.weixin.qq.com/s/sSRcfacmIbivUR-n5JxzgQ


该文章在 2026/5/16 8:48:43 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-2  粤公网安备44030602007207号