pin_drop当前位置:知识文库 ❯ 图文
JS集成:HTML script标签详解 - 详细教程与实战指南
一、教程简介
<script> 标签是 HTML 与 JavaScript 之间的桥梁,它负责将 JavaScript 代码嵌入或引入到 HTML 文档中。理解 <script> 标签的所有属性及其行为,对于构建高性能、安全可靠的 Web 应用至关重要。本教程将全面深入地讲解 <script> 标签的每一个属性、脚本加载与执行的时机、type 属性的各种取值,以及 <noscript> 标签的使用。
二、核心概念
script标签的基本作用
<script> 标签用于在 HTML 文档中嵌入或引用可执行脚本。它可以包含内联脚本代码,也可以通过 src 属性引用外部脚本文件。浏览器在解析 HTML 文档遇到 <script> 标签时,会根据标签的属性决定如何加载和执行脚本。
脚本加载与执行的基本流程
-
第一步:浏览器解析 HTML 文档,遇到
<script>标签 -
第二步:如果有
src属性,浏览器开始下载外部脚本 -
第三步:脚本下载完成后,浏览器执行脚本
-
第四步:脚本执行完毕后,浏览器继续解析后续 HTML
提示:这个基本流程会受到
async、defer等属性的影响而产生不同的行为。
三、语法与用法
基本语法
代码示例
<!-- 内联脚本 -->
<script>
console.log('Hello, World!');
</script>
<!-- 外部脚本 -->
<script src="app.js"></script>script标签的所有属性详解
1. src 属性
src 属性指定外部脚本文件的 URL。当设置了 src 属性后,<script> 标签内部的代码将被忽略。
代码示例
<!-- 正确用法:引用外部脚本 -->
<script src="js/app.js"></script>
<!-- 错误用法:同时使用 src 和内联代码,内联代码会被忽略 -->
<script src="js/app.js">
console.log('这行代码不会执行'); // 会被忽略
</script>
src 属性支持的 URL 类型:
代码示例
<!-- 相对路径 -->
<script src="./js/app.js"></script>
<!-- 绝对路径 -->
<script src="/assets/js/app.js"></script>
<!-- 完整 URL -->
<script src="https://cdn.example.com/library/v3.0/library.min.js"></script>
<!-- 协议相对 URL -->
<script src="//cdn.example.com/library.min.js"></script>
<!-- Data URI(不推荐用于生产环境) -->
<script src="data:text/javascript,console.log('Hello')"></script>2. type 属性
type 属性指定脚本的 MIME 类型,默认值为 text/javascript。现代浏览器中该属性可以省略。
module 类型的使用:
代码示例
<script type="module">
import { helper } from './utils.js';
helper();
</script>importmap 的使用:
代码示例
<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>JSON 数据块:
代码示例
<script type="application/json" id="config-data">
{
"apiEndpoint": "https://api.example.com",
"version": "2.1.0",
"debug": false
}
</script>
<script>
const configEl = document.getElementById('config-data');
const config = JSON.parse(configEl.textContent);
console.log(config.apiEndpoint);
</script>3. async 属性
async 属性是一个布尔属性,指示浏览器异步下载脚本,下载完成后立即执行,不等待 DOM 解析完成。
代码示例
<script src="analytics.js" async></script>4. defer 属性
defer 属性是一个布尔属性,指示浏览器异步下载脚本,但在 DOM 解析完成之后、DOMContentLoaded 事件触发之前执行。
代码示例
<script src="app.js" defer></script>5. crossorigin 属性
crossorigin 属性用于配置跨域资源的 CORS 请求,主要影响错误信息的获取。
代码示例
<!-- 不发送凭证的跨域请求 -->
<script src="https://cdn.example.com/lib.js" crossorigin></script>
<script src="https://cdn.example.com/lib.js" crossorigin="anonymous"></script>
<!-- 发送凭证的跨域请求 -->
<script src="https://auth.example.com/app.js" crossorigin="use-credentials"></script>6. integrity 属性
integrity 属性用于子资源完整性(SRI)校验,确保外部脚本未被篡改。值为一个或多个哈希值,格式为 哈希算法-Base64编码的哈希值。
代码示例
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous">
</script>生成 SRI 哈希的方法:
代码示例
# 使用 openssl 生成
cat bootstrap.bundle.min.js | openssl dgst -sha384 -binary | openssl base64 -A
# 使用 sri-cli 工具
npx sri-cli https://cdn.example.com/library.min.js7. nomodule 属性
nomodule 属性是一个布尔属性,用于在不支持 ES6 模块的浏览器中执行回退脚本。
代码示例
<!-- 支持模块的浏览器执行此脚本,忽略 nomodule 脚本 -->
<script type="module" src="app.modern.js"></script>
<!-- 不支持模块的浏览器执行此脚本,忽略 module 脚本 -->
<script nomodule src="app.legacy.js"></script>8. referrerpolicy 属性
referrerpolicy 属性指定获取脚本时发送的 Referer 请求头信息。
代码示例
<script
src="https://analytics.example.com/tracker.js"
referrerpolicy="no-referrer">
</script>9. blocking 属性
blocking 属性是较新的属性,用于指示脚本是否阻塞渲染。
代码示例
<script blocking="render" src="critical.js"></script>noscript 标签
<noscript> 标签用于在浏览器禁用 JavaScript 或不支持 JavaScript 时显示替代内容。
代码示例
<noscript>
<div style="padding: 20px; text-align: center; background: #f8d7da; color: #721c24;">
<h2>需要启用 JavaScript</h2>
<p>本网站依赖 JavaScript 运行,请在浏览器设置中启用 JavaScript 后刷新页面。</p>
</div>
</noscript>四、代码示例
示例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>
<!-- importmap 定义模块映射 -->
<script type="importmap">
{
"imports": {
"helpers": "./js/helpers.js"
}
}
</script>
<!-- defer 脚本:在 DOM 解析完成后执行 -->
<script src="js/main.js" defer></script>
<!-- async 脚本:下载完成后立即执行,适合独立的分析脚本 -->
<script src="js/analytics.js" async></script>
<!-- 带 SRI 校验的 CDN 脚本 -->
<script
src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-H6KKS1H1WwuERMSm+54dYLzjg0fKqDKUzFZ6P7vdPQH0vdMIhBbFsbQ5wYnESERlF"
crossorigin="anonymous">
</script>
<!-- 模块脚本 -->
<script type="module">
import { formatDate } from 'helpers';
console.log('当前日期:', formatDate(new Date()));
</script>
<!-- 回退脚本:不支持模块的浏览器执行 -->
<script nomodule>
console.log('您的浏览器不支持 ES6 模块,正在使用回退脚本');
</script>
<!-- JSON 配置数据 -->
<script type="application/json" id="app-config">
{
"theme": "dark",
"language": "zh-CN",
"apiBase": "https://api.example.com/v2"
}
</script>
</head>
<body>
<h1>script标签属性综合演示</h1>
<div id="output"></div>
<noscript>
<div class="no-js-warning">
<h2>请启用 JavaScript</h2>
<p>本页面需要 JavaScript 支持才能正常运行。</p>
</div>
</noscript>
<script>
// 读取 JSON 配置
const configEl = document.getElementById('app-config');
if (configEl) {
const config = JSON.parse(configEl.textContent);
document.getElementById('output').innerHTML =
'<p>主题:' + config.theme + '</p>' +
'<p>语言:' + config.language + '</p>' +
'<p>API地址:' + config.apiBase + '</p>';
}
</script>
</body>
</html>示例2:脚本加载顺序演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>脚本加载顺序演示</title>
<script>
console.log('[1] head 内联脚本 - 同步执行,阻塞解析');
</script>
<script src="js/deferred-a.js" defer></script>
<script src="js/deferred-b.js" defer></script>
<script src="js/async-a.js" async></script>
<script src="js/async-b.js" async></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('[DOMContentLoaded] DOM 解析完成');
});
</script>
</head>
<body>
<h1>脚本加载顺序演示</h1>
<p>请打开浏览器控制台查看脚本执行顺序</p>
<script>
console.log('[2] body 底部内联脚本 - 同步执行');
</script>
</body>
</html>预期输出顺序:
代码示例
[1] head 内联脚本 - 同步执行,阻塞解析
[2] body 底部内联脚本 - 同步执行
[deferred-a] defer 脚本 A 执行
[deferred-b] defer 脚本 B 执行(按声明顺序)
[DOMContentLoaded] DOM 解析完成
[async-a 或 async-b] async 脚本执行(谁先下载完谁先执行,顺序不确定)示例3:模块与回退脚本
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模块与回退脚本演示</title>
</head>
<body>
<h1>模块与回退脚本演示</h1>
<div id="result"></div>
<!-- 现代浏览器加载模块版本 -->
<script type="module">
const result = document.getElementById('result');
const version = 'ES6 Module 版本';
class App {
constructor(target) {
this.target = target;
}
render(message) {
this.target.textContent = message;
}
}
const app = new App(result);
app.render('运行在 ' + version + ' - 支持模块化、严格模式、独立作用域');
</script>
<!-- 旧浏览器加载回退版本 -->
<script nomodule>
var result = document.getElementById('result');
var version = '传统脚本版本';
result.textContent = '运行在 ' + version + ' - 使用传统脚本加载方式';
</script>
</body>
</html>示例4:importmap 与模块导入
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>importmap 与模块导入演示</title>
<!-- 定义导入映射 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/",
"lodash": "https://unpkg.com/lodash@4.17.21/lodash.js"
}
}
</script>
</head>
<body>
<h1>importmap 与模块导入演示</h1>
<div id="output"></div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const output = document.getElementById('output');
output.innerHTML = `
<p>Three.js 版本:${THREE.REVISION}</p>
<p>OrbitControls 已加载:${typeof OrbitControls !== 'undefined'}</p>
`;
</script>
</body>
</html>示例5:SRI 完整安全实践
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SRI 完整安全实践</title>
<!-- 带 SRI 校验的 CDN 资源 -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous">
</script>
<script>
// 监听 SRI 校验失败事件
window.addEventListener('securitypolicyviolation', function(event) {
console.error('安全策略违规:', {
违规资源: event.blockedURI,
违规指令: event.violatedDirective,
文档URI: event.documentURI
});
});
// 监听脚本加载错误
window.addEventListener('error', function(event) {
if (event.target && event.target.tagName === 'SCRIPT') {
console.error('脚本加载失败:', event.target.src);
loadFallbackScript();
}
}, true);
function loadFallbackScript() {
var fallbackScript = document.createElement('script');
fallbackScript.src = '/local-fallback/bootstrap.bundle.min.js';
document.head.appendChild(fallbackScript);
}
</script>
</head>
<body>
<h1>SRI 完整安全实践</h1>
<p>本页面演示了子资源完整性校验和加载失败回退机制</p>
</body>
</html>五、浏览器兼容性
script标签各属性兼容性
noscript标签兼容性
六、注意事项与最佳实践
1. 脚本放置位置
代码示例
<!-- 推荐:将脚本放在 body 底部或使用 defer -->
<body>
<!-- 页面内容 -->
<h1>页面内容</h1>
<p>用户可以看到的内容</p>
<!-- 脚本放在底部 -->
<script src="app.js"></script>
</body>
<!-- 或者使用 defer,脚本可以放在 head 中 -->
<head>
<script src="app.js" defer></script>
</head>2. 避免同时使用 async 和 defer
代码示例
<!-- 不推荐:同时使用 async 和 defer,async 优先级更高 -->
<script src="app.js" async defer></script>
<!-- 推荐:根据需求选择其中一个 -->
<script src="independent.js" async></script>
<script src="app.js" defer></script>3. 内联脚本与外部脚本的分离
代码示例
<!-- 不推荐:内联脚本依赖外部脚本但未保证顺序 -->
<script src="library.js" async></script>
<script>
// library 可能还未加载
library.doSomething();
</script>
<!-- 推荐:使用回调或事件确保依赖关系 -->
<script src="library.js" async id="lib-script"></script>
<script>
document.getElementById('lib-script').addEventListener('load', function() {
library.doSomething();
});
</script>4. SRI 安全实践
-
所有从 CDN 加载的第三方脚本都应使用
integrity属性 -
始终配合
crossorigin="anonymous"使用 -
定期更新哈希值以匹配脚本更新
5. 模块脚本的注意事项
-
严格模式:模块脚本默认启用严格模式
-
独立作用域:模块脚本有自己的顶层作用域,不会污染全局
-
defer行为:模块脚本默认
defer行为 -
单例模式:同一模块只会执行一次
-
CORS限制:模块脚本受 CORS 限制
七、代码规范示例
推荐的 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. importmap 放在最前面 -->
<script type="importmap">
{
"imports": {
"app-utils": "/js/utils/index.js"
}
}
</script>
<!-- 2. 第三方库使用 CDN + SRI + crossorigin -->
<script
src="https://cdn.jsdelivr.net/npm/vue@3.3.8/dist/vue.global.prod.js"
integrity="sha384-VU1VPn5bJx6v8GjQfFf0v8eFZ9r0F5WZ6F5eF6v8eFZ9r0F5WZ6F5eF6v8eFZ9r0F"
crossorigin="anonymous">
</script>
<!-- 3. 应用脚本使用 defer -->
<script src="/js/app.js" defer></script>
<script src="/js/router.js" defer></script>
<script src="/js/store.js" defer></script>
<!-- 4. 独立的分析脚本使用 async -->
<script src="/js/analytics.js" async></script>
</head>
<body>
<!-- 页面内容 -->
<!-- 5. noscript 放在 body 中 -->
<noscript>
<p>本应用需要启用 JavaScript 才能正常运行。</p>
</noscript>
</body>
</html>八、常见问题与解决方案
常见问题
脚本执行时 DOM 元素不存在怎么办?
有三种解决方案:1)使用 defer 属性,让脚本在 DOM 解析完成后执行;2)使用 DOMContentLoaded 事件包裹代码;3)将脚本放在 body 底部,确保 DOM 元素已解析。
async 脚本执行顺序不确定如何解决?
有依赖关系的脚本应使用 defer(保证声明顺序执行),或将多个脚本合并为一个文件用 async 加载,也可以使用动态加载(Promise)来确保顺序。
CORS 错误导致脚本加载失败怎么处理?
确保服务器返回正确的 CORS 头(如 Access-Control-Allow-Origin: *),并在 script 标签上添加 crossorigin 属性。如需发送凭证,使用 crossorigin="use-credentials"。
importmap 重复定义报错怎么解决?
一个页面只能有一个 importmap。如果有多个模块映射需求,需要将所有的映射合并到一个 <script type="importmap"> 标签中。
如何保证 CDN 脚本的安全性?
使用 integrity 属性进行子资源完整性校验(SRI),配合 crossorigin="anonymous" 使用。同时监听 error 事件实现加载失败回退机制。
type="module" 和传统脚本有什么区别?
模块脚本默认严格模式、有独立作用域不污染全局、默认 defer 行为、受 CORS 限制、同一模块只执行一次。可使用 nomodule 属性为旧浏览器提供回退方案。
九、总结
<script> 标签虽然看似简单,但其属性和行为涉及了 Web 性能优化、安全性、模块化等多个重要领域。以下是核心要点回顾:
-
src:用于引入外部脚本,设置后内联代码将被忽略
-
type:决定脚本类型,
module和importmap是现代开发的重要特性 -
async:适合独立脚本(如分析工具),下载完即执行,顺序不可控
-
defer:适合需要操作 DOM 的脚本,保证按声明顺序执行
-
crossorigin 和 integrity:保障跨域脚本安全的重要属性
-
nomodule:提供优雅的浏览器兼容性降级方案
-
noscript:为禁用 JavaScript 的用户提供替代内容
总结:掌握这些属性的用法,能够帮助开发者在不同场景下做出正确的选择,构建出性能优异、安全可靠的 Web 应用。
本文涉及AI创作
内容由AI创作,请仔细甄别