pin_drop当前位置:知识文库 ❯ 图文
元信息:HTML script标签 - 完整教程与代码示例
教程简介
HTML <script> 标签是引入JavaScript代码的核心机制,其属性配置直接影响页面的加载性能和执行顺序。async 和 defer 的选择、type 属性的演变、crossorigin 的安全配置——每一个细节都可能对用户体验产生显著影响。本教程将深入讲解 <script> 标签的所有属性及其对页面性能的影响。
核心概念
script 标签的加载与执行模型
浏览器在解析HTML时遇到 <script> 标签,默认行为是:
-
暂停HTML解析
-
下载脚本文件(如果是外部脚本)
-
执行脚本
-
恢复HTML解析
这种默认行为会阻塞页面渲染,因此需要通过 async、defer 等属性优化加载策略。
script 标签的核心属性
语法与用法
1. 内联脚本与外部脚本
代码示例
<!-- 内联脚本:直接在标签内编写代码 -->
<script>
console.log('这是内联脚本');
</script>
<!-- 外部脚本:通过src引入 -->
<script src="app.js"></script>
<!-- 注意:同时使用src和内联代码时,内联代码会被忽略 -->
<script src="app.js">
console.log('这行代码不会执行'); // 被忽略
</script>2. type 属性
代码示例
<!-- 传统JavaScript(HTML5中可省略type) -->
<script type="text/javascript">
console.log('传统写法');
</script>
<!-- HTML5简写(推荐) -->
<script>
console.log('推荐写法');
</script>
<!-- ES模块 -->
<script type="module">
import { helper } from './utils.js';
helper();
</script>
<!-- Import Map:定义模块标识符映射 -->
<script type="importmap">
{
"imports": {
"lodash": "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.js",
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js"
}
}
</script>
<script type="module">
import _ from 'lodash';
import * as THREE from 'three';
</script>
<!-- 数据块:不被浏览器执行的代码块 -->
<script type="application/json" id="config">
{
"apiUrl": "https://api.example.com",
"version": "2.0"
}
</script>
<script type="text/plain" id="template">
<div class="card">
<h3>{{title}}</h3>
<p>{{content}}</p>
</div>
</script>3. async 与 defer 详解
这是 <script> 标签最重要的性能优化属性。
代码示例
<!-- 默认行为:阻塞解析,立即下载并执行 -->
<script src="app.js"></script>
<!-- defer:异步下载,DOM解析完成后按顺序执行 -->
<script defer src="app.js"></script>
<!-- async:异步下载,下载完成后立即执行(不保证顺序) -->
<script async src="analytics.js"></script>三种加载模式对比:
代码示例
默认(无属性):
HTML解析: ████████░░░░████████████
脚本下载: ████████
脚本执行: ████████
defer:
HTML解析: ████████████████████████
脚本下载: ████████
脚本执行: ████ (DOM解析完成后)
async:
HTML解析: ████████████████████████
脚本下载: ████████
脚本执行: ████ (下载完成后立即)async vs defer 选择指南:
4. crossorigin 属性
代码示例
<!-- anonymous:不发送凭证(Cookie等) -->
<script crossorigin="anonymous" src="https://cdn.example.com/lib.js"></script>
<!-- use-credentials:发送凭证 -->
<script crossorigin="use-credentials" src="https://auth.example.com/data.js"></script>
<!-- 不设置crossorigin:使用默认行为,但无法获取错误详情 -->
<script src="https://cdn.example.com/lib.js"></script>crossorigin 与错误处理:
代码示例
// 不设置 crossorigin 时,跨域脚本错误只能捕获到 "Script error."
window.onerror = function(msg, url, line) {
console.log(msg); // "Script error." - 无法获取真实错误信息
};
// 设置 crossorigin="anonymous" 后,可以获取真实错误信息
// 前提:服务器返回 Access-Control-Allow-Origin 头5. integrity 属性(SRI)
代码示例
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bk"
crossorigin="anonymous"
></script>6. nomodule 属性
代码示例
<!-- 支持ES模块的浏览器执行此脚本 -->
<script type="module" src="app.modern.js"></script>
<!-- 不支持ES模块的浏览器执行此脚本 -->
<script nomodule src="app.legacy.js"></script>
<!-- 两者互斥:现代浏览器忽略nomodule,旧浏览器忽略type="module" -->代码示例
示例1:script 标签加载策略对比
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>script标签加载策略对比</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #0d1117;
color: #c9d1d9;
padding: 24px;
}
.container {
max-width: 900px;
margin: 0 auto;
}
h1 {
color: #58a6ff;
margin-bottom: 32px;
font-size: 24px;
text-align: center;
}
.strategy {
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
}
.strategy h3 {
color: #f0883e;
margin-bottom: 16px;
font-size: 16px;
}
.timeline-bar {
position: relative;
height: 40px;
background: #21262d;
border-radius: 8px;
margin-bottom: 8px;
overflow: hidden;
}
.timeline-bar .segment {
position: absolute;
top: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 600;
color: #fff;
border-radius: 4px;
}
.segment.parse { background: #238636; }
.segment.download { background: #1f6feb; }
.segment.execute { background: #da3633; }
.segment.blocked { background: #484f58; }
.legend {
display: flex;
gap: 16px;
margin-top: 16px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
}
.legend-color {
width: 16px;
height: 16px;
border-radius: 3px;
}
.code-snippet {
background: #0d1117;
border: 1px solid #30363d;
padding: 12px 16px;
border-radius: 8px;
font-family: "Cascadia Code", monospace;
font-size: 13px;
color: #79c0ff;
margin-top: 12px;
}
.recommendation {
display: inline-block;
padding: 4px 10px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
margin-top: 8px;
}
.rec-avoid { background: #3d1f1f; color: #f85149; }
.rec-use { background: #1f3d1f; color: #3fb950; }
.rec-case { background: #1f2d3d; color: #58a6ff; }
</style>
</head>
<body>
<div class="container">
<h1>script 标签加载策略对比</h1>
<div class="strategy">
<h3>默认(无属性)</h3>
<div class="timeline-bar">
<div class="segment parse" style="left:0; width:35%;">HTML解析</div>
<div class="segment blocked" style="left:35%; width:25%;">阻塞</div>
<div class="segment download" style="left:35%; width:20%;">下载</div>
<div class="segment execute" style="left:55%; width:10%;">执行</div>
<div class="segment parse" style="left:65%; width:35%;">继续解析</div>
</div>
<div class="code-snippet"><script src="app.js"></script></div>
<span class="recommendation rec-avoid">不推荐 - 阻塞渲染</span>
</div>
<div class="strategy">
<h3>defer - 延迟执行</h3>
<div class="timeline-bar">
<div class="segment parse" style="left:0; width:70%;">HTML解析(不中断)</div>
<div class="segment download" style="left:20%; width:20%;">异步下载</div>
<div class="segment execute" style="left:70%; width:15%;">DOM解析后执行</div>
</div>
<div class="code-snippet"><script defer src="app.js"></script></div>
<span class="recommendation rec-use">推荐 - 不阻塞解析,按顺序执行</span>
</div>
<div class="strategy">
<h3>async - 异步执行</h3>
<div class="timeline-bar">
<div class="segment parse" style="left:0; width:85%;">HTML解析(不中断)</div>
<div class="segment download" style="left:20%; width:20%;">异步下载</div>
<div class="segment execute" style="left:40%; width:10%;">下载完即执行</div>
<div class="segment blocked" style="left:40%; width:10%;">短暂阻塞</div>
</div>
<div class="code-snippet"><script async src="analytics.js"></script></div>
<span class="recommendation rec-case">适用场景 - 独立脚本,不依赖顺序</span>
</div>
<div class="strategy">
<h3>type="module" - ES模块</h3>
<div class="timeline-bar">
<div class="segment parse" style="left:0; width:70%;">HTML解析(不中断)</div>
<div class="segment download" style="left:20%; width:20%;">异步下载</div>
<div class="segment execute" style="left:70%; width:15%;">类似defer执行</div>
</div>
<div class="code-snippet"><script type="module" src="app.js"></script></div>
<span class="recommendation rec-use">推荐 - 默认defer行为,严格模式</span>
</div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background:#238636;"></div>
HTML解析
</div>
<div class="legend-item">
<div class="legend-color" style="background:#1f6feb;"></div>
脚本下载
</div>
<div class="legend-item">
<div class="legend-color" style="background:#da3633;"></div>
脚本执行
</div>
<div class="legend-item">
<div class="legend-color" style="background:#484f58;"></div>
解析阻塞
</div>
</div>
</div>
</body>
</html>示例2:完整的 script 标签配置模板
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>script标签完整配置模板</title>
<!-- ========== 1. 预连接CDN ========== -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<!-- ========== 2. 预加载关键脚本 ========== -->
<link rel="preload" href="js/critical.js" as="script">
<!-- ========== 3. 内联关键脚本(极少量) ========== -->
<script>
// 关键性能指标收集或A/B测试等必须尽早执行的代码
window.performanceMark = performance.now();
</script>
<!-- ========== 4. 外部脚本 - defer(主应用逻辑) ========== -->
<script defer src="js/vendor.js"></script>
<script defer src="js/app.js"></script>
<!-- ========== 5. 外部脚本 - async(独立脚本) ========== -->
<script async src="https://www.google-analytics.com/analytics.js"></script>
<!-- ========== 6. 带SRI的CDN脚本 ========== -->
<script
defer
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bk"
crossorigin="anonymous"
></script>
<!-- ========== 7. ES模块 ========== -->
<script type="module">
import { init } from './js/app-module.js';
init();
</script>
<!-- ========== 8. 旧浏览器降级 ========== -->
<script nomodule>
// 不支持ES模块的浏览器执行此脚本
document.write('<script src="js/app.legacy.js"><\/script>');
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f8f9fa;
padding: 24px;
}
.container {
max-width: 800px;
margin: 0 auto;
}
h1 {
color: #1a73e8;
margin-bottom: 24px;
font-size: 24px;
}
.config-item {
background: #fff;
border-radius: 12px;
padding: 20px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border-left: 4px solid #1a73e8;
}
.config-item h3 {
font-size: 14px;
color: #1a73e8;
margin-bottom: 8px;
}
.config-item pre {
background: #263238;
color: #80cbc4;
padding: 12px;
border-radius: 6px;
font-size: 13px;
overflow-x: auto;
line-height: 1.6;
}
.config-item p {
color: #666;
font-size: 14px;
margin-top: 8px;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<h1>script 标签完整配置模板</h1>
<div class="config-item">
<h3>1. 预连接CDN</h3>
<pre><link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin></pre>
<p>提前建立与CDN的连接,减少脚本下载延迟。</p>
</div>
<div class="config-item">
<h3>2. 内联关键脚本</h3>
<pre><script>
window.performanceMark = performance.now();
</script></pre>
<p>仅内联必须尽早执行的极少量代码,如性能标记。</p>
</div>
<div class="config-item">
<h3>3. defer - 主应用逻辑</h3>
<pre><script defer src="js/vendor.js"></script>
<script defer src="js/app.js"></script></pre>
<p>主逻辑使用defer,保证DOM解析完成后按顺序执行。</p>
</div>
<div class="config-item">
<h3>4. async - 独立脚本</h3>
<pre><script async src="analytics.js"></script></pre>
<p>分析、广告等独立脚本使用async,不阻塞页面。</p>
</div>
<div class="config-item">
<h3>5. SRI + crossorigin</h3>
<pre><script defer
src="https://cdn.../bootstrap.min.js"
integrity="sha384-..."
crossorigin="anonymous">
</script></pre>
<p>CDN脚本使用SRI校验完整性,crossorigin获取错误详情。</p>
</div>
</div>
</body>
</html>示例3:defer 与 async 执行顺序演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>defer与async执行顺序演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
padding: 24px;
}
.container { max-width: 800px; margin: 0 auto; }
h1 { color: #1a73e8; margin-bottom: 24px; font-size: 24px; }
.log-area {
background: #1e1e1e;
color: #d4d4d4;
padding: 20px;
border-radius: 12px;
font-family: "Cascadia Code", "Courier New", monospace;
font-size: 13px;
line-height: 2;
min-height: 200px;
}
.log-area .time { color: #6a9955; }
.log-area .event { color: #569cd6; }
.log-area .defer-log { color: #4ec9b0; }
.log-area .async-log { color: #ce9178; }
.log-area .dom-log { color: #dcdcaa; }
.btn-group { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 14px;
cursor: pointer;
font-weight: 500;
}
.btn-run { background: #1a73e8; color: #fff; }
.btn-clear { background: #666; color: #fff; }
.explanation {
background: #fff;
border-radius: 12px;
padding: 20px;
margin-top: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.explanation h3 { color: #333; margin-bottom: 12px; }
.explanation p { color: #666; font-size: 14px; line-height: 1.8; }
</style>
</head>
<body>
<div class="container">
<h1>defer 与 async 执行顺序演示</h1>
<div class="btn-group">
<button class="btn-run" onclick="runDemo()">运行演示</button>
<button class="btn-clear" onclick="clearLog()">清空日志</button>
</div>
<div class="log-area" id="logArea">
<div class="time">等待运行...</div>
</div>
<div class="explanation">
<h3>关键区别</h3>
<p><strong>defer:</strong>多个defer脚本按声明顺序执行,在DOMContentLoaded事件之前执行。适合有依赖关系的脚本。</p>
<p><strong>async:</strong>多个async脚本谁先下载完谁先执行,不保证顺序。适合独立的第三方脚本。</p>
</div>
</div>
<script>
var logArea = document.getElementById('logArea');
function addLog(text, className) {
var time = new Date().toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', fractionalSecondDigits: 3 });
var div = document.createElement('div');
div.innerHTML = '<span class="time">[' + time + ']</span> <span class="' + className + '">' + text + '</span>';
logArea.appendChild(div);
}
function clearLog() {
logArea.innerHTML = '<div class="time">日志已清空</div>';
}
function runDemo() {
clearLog();
// 模拟HTML解析开始
addLog('HTML解析开始', 'event');
// 模拟defer脚本按顺序执行
setTimeout(function() {
addLog('defer脚本1 (vendor.js) 开始执行', 'defer-log');
}, 200);
setTimeout(function() {
addLog('defer脚本2 (app.js) 开始执行', 'defer-log');
}, 400);
setTimeout(function() {
addLog('defer脚本3 (utils.js) 开始执行', 'defer-log');
}, 600);
// 模拟async脚本不按顺序执行
var asyncOrder = [3, 1, 2]; // 模拟下载完成顺序与声明顺序不同
var asyncDelays = [300, 500, 700];
for (var i = 0; i < 3; i++) {
(function(index, order) {
setTimeout(function() {
addLog('async脚本' + order + ' 下载完成并执行', 'async-log');
}, asyncDelays[i]);
})(i, asyncOrder[i]);
}
// DOMContentLoaded
setTimeout(function() {
addLog('DOMContentLoaded 事件触发', 'dom-log');
}, 800);
// 模拟HTML解析完成
setTimeout(function() {
addLog('HTML解析完成', 'event');
}, 100);
}
// 监听真实的DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
addLog('真实 DOMContentLoaded 已触发', 'dom-log');
});
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 脚本位置选择
代码示例
<!-- 推荐:现代做法 - 所有外部脚本使用defer,放在head中 -->
<head>
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
</head>
<!-- 传统做法 - 放在body末尾(仍然可行) -->
<body>
<!-- 页面内容 -->
<script src="vendor.js"></script>
<script src="app.js"></script>
</body>2. defer 脚本的执行顺序
代码示例
<!-- defer脚本按声明顺序执行 -->
<script defer src="1-jquery.js"></script> <!-- 第1个执行 -->
<script defer src="2-plugin.js"></script> <!-- 第2个执行 -->
<script defer src="3-app.js"></script> <!-- 第3个执行 -->
<!-- async脚本不保证顺序 -->
<script async src="analytics.js"></script> <!-- 可能第1或第2执行 -->
<script async src="ads.js"></script> <!-- 可能第1或第2执行 -->3. 内联脚本与外部脚本的依赖
代码示例
<!-- 错误:内联脚本依赖defer脚本,但会先执行 -->
<script defer src="jquery.js"></script>
<script>
// 这会在jquery.js之前执行!
$('body').addClass('loaded'); // $ is not defined
</script>
<!-- 正确方案1:内联脚本也使用DOMContentLoaded -->
<script defer src="jquery.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('body').addClass('loaded');
});
</script>
<!-- 正确方案2:将内联代码移到defer脚本之后,使用load事件 -->
<script defer src="app.js"></script>
<script>
window.addEventListener('load', function() {
// 所有脚本都已加载完成
});
</script>4. 动态创建脚本的默认行为
代码示例
// 动态创建的script默认是async行为
var script = document.createElement('script');
script.src = 'app.js';
// 等同于 <script async src="app.js"></script>
// 如需按顺序执行,显式设置async为false
script.async = false;
document.head.appendChild(script);5. ES模块的注意事项
代码示例
<!-- type="module" 默认具有defer行为 -->
<script type="module" src="app.js"></script>
<!-- 等同于 -->
<script defer type="module" src="app.js"></script>
<!-- module脚本中不能再加async/defer(async可以加) -->
<script async type="module" src="app.js"></script>
<!-- async module:下载完成后立即执行,不等待DOM -->
<!-- module脚本默认使用严格模式 -->
<script type="module">
// 此处自动启用严格模式
// this 在顶层为 undefined(非window)
</script>代码规范示例
标准项目 script 配置规范
代码示例
<head>
<!-- 1. 预连接 -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<!-- 2. 内联关键脚本(仅极少量,< 1KB) -->
<script>
// 性能标记、特征检测等
window.__perfStart = Date.now();
</script>
<!-- 3. 第三方库(defer + SRI) -->
<script
defer
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-..."
crossorigin="anonymous"
></script>
<!-- 4. 应用脚本(defer,按依赖顺序) -->
<script defer src="/js/vendor.js"></script>
<script defer src="/js/app.js"></script>
<!-- 5. 独立第三方脚本(async) -->
<script async src="https://www.google-analytics.com/analytics.js"></script>
</head>常见问题与解决方案
常见问题
Q1:defer 在 IE9 及以下有 bug,怎么处理?
解决方案:IE9 中 defer 脚本可能不按顺序执行。如果需要支持 IE9,将脚本放在 </body> 之前,或使用条件注释。
代码示例
<!--[if lte IE 9]>
<script src="vendor.js"></script>
<script src="app.js"></script>
<![endif]-->
<!--[if gt IE 9]><!-->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script>
<!--<![endif]-->Q2:async 和 defer 可以同时使用吗?
解决方案:可以,但 async 优先。如果浏览器支持 async,则按 async 行为执行;如果不支持 async,则回退到 defer。这是一种渐进增强策略。
代码示例
<script async defer src="analytics.js"></script>
<!-- 支持 async 的浏览器:async 行为 -->
<!-- 不支持 async 的浏览器:defer 行为 -->Q3:如何获取跨域脚本的详细错误信息?
解决方案:
-
在
<script>标签添加crossorigin="anonymous" -
服务器返回
Access-Control-Allow-Origin: *或具体域名 -
使用
window.onerror捕获完整错误信息
Q4:type="module" 和普通 script 的区别?
Q5:如何实现脚本的懒加载?
解决方案:
代码示例
// 方案1:IntersectionObserver 触发加载
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var script = document.createElement('script');
script.src = entry.target.dataset.src;
entry.target.appendChild(script);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('[data-src]').forEach(function(el) {
observer.observe(el);
});
// 方案2:用户交互触发
document.getElementById('loadBtn').addEventListener('click', function() {
var script = document.createElement('script');
script.src = 'heavy-module.js';
document.head.appendChild(script);
});总结
HTML <script> 标签的属性配置对页面性能有直接影响。理解不同加载模式的行为差异,选择合适的策略,是前端性能优化的基础。
核心要点回顾:
-
默认行为阻塞渲染:无属性的
<script>会暂停HTML解析,应尽量避免 -
defer 是首选:主应用逻辑使用
defer,不阻塞解析,按顺序执行 -
async 用于独立脚本:分析、广告等不依赖DOM的脚本使用
async -
type="module" 默认 defer:ES模块脚本自动延迟执行
-
SRI 保障CDN安全:外部CDN脚本应添加
integrity校验 -
crossorigin 获取错误详情:跨域脚本添加
crossorigin以获取真实错误信息 -
nomodule 实现降级:配合
type="module"为旧浏览器提供兼容脚本 -
脚本位置放在 head:配合
defer,脚本放在<head>中可以更早开始下载
合理配置 <script> 标签,是提升页面加载速度和用户体验的第一步。
本文涉及AI创作
内容由AI创作,请仔细甄别