在 Web 开发中,<script>
标签不仅仅是一个简单的 JavaScript 加载器------它是 页面交互的核心驱动者、性能优化的关键杠杆,同时也是安全防御的前沿阵地 。从传统的脚本加载到现代模块化开发,从基础的阻塞渲染到精细的优先级控制,<script>
标签的多面性决定了 Web 应用的体验与安全性。
一、属性概述
基础加载控制
属性 | 作用 | 示例 |
---|---|---|
src |
指定外部脚本的 URL | <script src="app.js"></script> |
type |
定义脚本类型(默认 text/javascript ,现代可省略) |
<script type="module"> |
async |
异步加载脚本,不阻塞 HTML 解析(适用于无依赖的脚本) | <script src="analytics.js" async> |
defer |
延迟执行脚本,在 HTML 解析完成后按顺序执行(适用于依赖 DOM 的脚本) | <script src="vendor.js" defer> |
安全与完整性校验
属性 | 作用 | 示例 |
---|---|---|
integrity |
提供脚本的哈希值(SHA-256/384/512),防止 CDN 劫持 | <script src="lib.js" integrity="sha384-xxx..." crossorigin> |
crossorigin |
控制跨域请求的 CORS 行为(anonymous /use-credentials ) |
<script src="https://cdn.com/lib.js" crossorigin="anonymous"> |
referrerpolicy |
控制引用来源策略(如 no-referrer 、strict-origin ) |
<script src="track.js" referrerpolicy="no-referrer"> |
模块化与现代特性
属性 | 作用 | 示例 |
---|---|---|
nomodule |
仅在不支持 ES Modules 的旧浏览器中执行该脚本 | <script nomodule src="legacy.js"> |
nonce |
配合 CSP(内容安全策略)允许内联脚本执行 | <script nonce="r4nd0m123"> |
性能优化
属性 | 作用 | 示例 |
---|---|---|
fetchpriority |
提示浏览器加载优先级(high /low /auto ) |
<script src="critical.js" fetchpriority="high"> |
动态脚本控制
属性 | 作用 | 示例 |
---|---|---|
onload |
脚本加载完成时触发的事件 | <script src="app.js" onload="init()"> |
onerror |
脚本加载失败时触发的事件 | <script src="fail.js" onerror="fallback()"> |
async 与 defer
async
用于控制脚本的加载和执行行为,当存在时表示脚本异步加载,特别适用于现代Web性能优化。
xml
<script src="script.js" async></script>
异步加载:
- 脚本的下载不会阻塞HTML解析
- 浏览器在解析HTML的同时并行下载脚本
执行时机:
- 脚本一旦下载完成就立即执行
- 执行时会阻塞HTML解析(与下载阶段的非阻塞不同)
顺序不确定性:
- 多个
async
脚本不保证执行顺序 - 先下载完成的脚本先执行
适合使用 async 的场景:
- 独立第三方脚本:分析工具、广告脚本、社交媒体插件
- 不影响关键渲染路径的脚本:不需要立即执行的辅助功能、延迟加载的非核心功能
- 不依赖DOM就绪的脚本:不需要等待DOM完全加载的脚本
- 不依赖其他脚本的独立功能:自包含的模块或功能
- 性能优化:想让脚本不阻塞页面的渲染
defer
用于优化脚本加载和执行时机,当存在时表示脚本延迟执行,特别适用于需要保持执行顺序但又不想阻塞页面渲染的场景。
xml
<script src="script.js" defer></script>
异步加载:
- 脚本的下载不会阻塞HTML解析
- 与
async
类似,浏览器会并行下载脚本
延迟执行:
- 脚本执行会延迟到 DOM解析完成之后 、DOMContentLoaded事件触发之前
- 保证在页面完全解析后才执行
顺序保证:
- 多个
defer
脚本会严格按照它们在HTML中出现的顺序执行 - 这是与
async
最主要的区别
适合使用 defer 的场景:
- 依赖DOM的脚本:需要操作 DOM,但不需要立即执行
- 多个脚本需要按顺序执行 :
defer
会保持脚本的加载顺序,而async
不会 - 优化页面渲染性能 :避免阻塞 HTML 解析,提升页面加载速度;适用于
<head>
中的脚本 - 关键但不紧急的脚本: 需要尽早加载但不必立即执行的代码
特性 | 普通脚本 | async |
defer |
---|---|---|---|
加载阻塞HTML | 是 | 否 | 否 |
执行阻塞HTML | 是 | 是(执行时) | 否 |
执行时机 | 立即执行 | 下载完成后立即执行 | DOM解析完成后按序执行 |
执行顺序 | 按文档顺序 | 下载完成顺序 | 按文档顺序 |
DOM准备状态 | 不确定 | 不确定 | DOM已完全解析 |
注意事项:
defer
属性只对外部脚本有效,内联脚本(没有src属性)的脚本会立即执行defer
与async
具有互斥性,现代浏览器会有限使用async
- 通过javascript动态创建的脚本默认具有
async
行为 - IE9以下的浏览器不完全保证
defer
脚本的执行顺序 - ES模块默认具有
defer
行为
欢迎访问我的个人网站:www.dengzhanyong.com
关注公众号【前端筱园】,不错过每一篇推送。
加入【交流群】,共同学习成长
type属性
传统 JavaScript (text/javascript)
- 默认类型,可省略不写
- 代码在全局作用域执行
- 会阻塞HTML解析(除非使用async/defer)
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>传统JavaScript示例</title>
<!-- 显式声明type -->
<script type="text/javascript">
function sayHello() {
alert('Hello from text/javascript!');
}
</script>
<!-- HTML5中可省略type -->
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('btn').addEventListener('click', sayHello);
});
</script>
</head>
<body>
<button id="btn">点击我</button>
</body>
</html>
ES6 模块 (module)
- 支持
import/export
语法 - 默认严格模式
- 自动延迟执行(类似defer)
- 需要服务器环境(本地文件有CORS限制)
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ES6模块示例</title>
<!-- 主模块 -->
<script type="module">
import { greet } from './greet.js';
import utils from './utils.js';
greet('Alice'); // 输出: Hello, Alice!
console.log(utils.add(2, 3)); // 输出: 5
</script>
</head>
<body>
<h1>模块化示例</h1>
</body>
</html>
greet.js:
javascript
export function greet(name) {
console.log(`Hello, ${name}!`);
}
utils.js:
javascript
export default {
add(a, b) {
return a + b;
}
};
客户端模板 (text/template)
- 不会被浏览器当作JS执行
- 常用于早期前端框架的模板存储
- 可通过DOM API获取内容
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>客户端模板示例</title>
<script type="text/template" id="user-template">
<div class="user-card">
<img src="{{avatar}}" alt="{{name}}">
<h2>{{name}}</h2>
<p>{{bio}}</p>
</div>
</script>
</head>
<body>
<div id="app"></div>
<script>
const template = document.getElementById('user-template').innerHTML;
const data = {
avatar: 'https://resource.dengzhanyong.com/images/749d2476-066e-4bdc-b7fc-2a9c799a18eb.jpg',
name: '张三',
bio: '前端开发者'
};
// 简单的模板渲染函数
function render(template, data) {
return template.replace(/{{(\w+)}}/g, (_, key) => data[key] || '');
}
document.getElementById('app').innerHTML = render(template, data);
</script>
</body>
</html>
JSON 数据块 (application/json
)
- 安全的内联JSON存储方式
- 比直接JS变量更规范
- 避免JSON被当作JS执行的安全风险
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSON数据块示例</title>
<script type="application/json" id="site-config">
{
"features": {
"darkMode": true,
"notifications": false
},
"theme": {
"primaryColor": "#4285f4",
"secondaryColor": "#34a853"
}
}
</script>
<style>
body {
background: var(--primary);
}
h1 {
color: var(--secondary);
}
</style>
</head>
<body>
<h1>JSON数据块示例</h1>
<div>
主题色:<span id="info"></span>
</div>
</body>
<script>
const config = JSON.parse(document.getElementById('site-config').textContent);
const infoDom = document.getElementById("info");
console.log('配置信息:', config);
console.log('主题颜色:', config.theme.primaryColor);
infoDom.innerHTML = config.theme.primaryColor;
// 应用配置
document.body.style.setProperty('--primary', config.theme.primaryColor);
document.body.style.setProperty('--secondary', config.theme.secondaryColor);
</script>
</html>
Import Maps (importmap
)
- 控制模块解析路径
- 替换裸模块说明符
- 需要较新浏览器支持
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Import Maps示例</title>
<script type="importmap">
{
"imports": {
"lodash": "https://cdn.jsdelivr.net/npm/[email protected]/lodash.js",
"app/": "/htmlapi/scriptlabel/"
}
}
</script>
</head>
<body>
<script type="module">
import { chunk } from 'lodash';
import utils from 'app/utils.js';
console.log(chunk([1, 2, 3, 4], 2));
console.log(utils.add(1, 2));
</script>
</body>
</html>
JSX - React(text/babel)
- 需要Babel等转译器
- 直接在HTML中写JSX
- 适合简单演示,生产环境建议预编译
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>React 18 Example</title>
</head>
<body>
<div id="root"></div>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function App() {
return <h1>Hello, React 18!</h1>;
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>
Vue 单文件组件风格 (text/x-template
)
- Vue特有的模板定义方式
- 分离模板和逻辑
- 需要Vue库支持
在线示例:www.dengzhanyong.com/htmlapi/scr...
xml
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Vue模板示例</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/x-template" id="user-card">
<div class="user">
<h2>{{ user.name }}</h2>
<p>Email: {{ user.email }}</p>
<button @click="logUser">Log Info</button>
</div>
</script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
user: {
name: 'Alice',
email: '[email protected]'
}
}
},
methods: {
logUser() {
console.log('User:', this.user);
}
},
template: '#user-card'
}).mount('#app');
</script>
</body>
</html>
以上不同类型的对比如下:
类型 | 案例 | 主要用途 | 是否执行 |
---|---|---|---|
text/javascript |
传统JS代码 | 常规脚本 | 是 |
module |
ES6模块 | 现代模块化开发 | 是 |
text/template |
客户端模板 | 存储HTML模板 | 否 |
application/json |
配置数据 | 存储结构化数据 | 否 |
importmap |
模块映射 | 控制模块解析 | 否 |
text/babel |
JSX代码 | React组件 | 需转译 |
text/x-template |
Vue模板 | Vue组件模板 | 否 |
crossorigin属性
用于控制跨域脚本请求的CORS(跨源资源共享)行为,主要影响错误信息的获取和资源的完整性验证。
xml
<script src="https://other-domain.com/script.js" crossorigin></script>
不设置 crossorigin
(默认情况)
-
浏览器会以 匿名模式(anonymous) 加载脚本:
- 不会发送凭据(如 cookies、HTTP 认证等)。
- 如果脚本加载失败(如语法错误),浏览器控制台 无法获取详细的错误信息 (只能看到
Script error.
)。
-
适用于:
- 加载第三方库(如 jQuery、React CDN),不需要获取详细错误信息时。
设置 crossorigin
-
crossorigin="anonymous"
:- 请求脚本时 不带凭据(如 cookies)。
- 但允许浏览器 捕获更详细的错误信息(如行号、错误描述)。
-
crossorigin="use-credentials"
:- 请求脚本时 带凭据(如 cookies、HTTP 认证)。
- 需要服务器明确允许(通过 CORS 头
Access-Control-Allow-Credentials: true
)。
integrity属性
integrity
属性是 Subresource Integrity (SRI) 的一部分,用于验证外部脚本/样式表的完整性,防止资源被篡改。
ini
<script
src="URL"
integrity="哈希算法-哈希值"
crossorigin="anonymous">
</script>
作用原理:
- 浏览器下载脚本前会先计算其哈希值
- 将计算结果与
integrity
提供的哈希值比对 - 只有匹配时才会执行脚本
指定多个哈希值
包含多个哈希值(用空格分隔),格式为 alg-hash
(如 sha256-xxx
、sha384-xxx
、sha512-xxx
),览器会依次尝试匹配,只要有一个哈希值验证通过即可加载资源。
ini
<script
src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"
integrity="sha512-3m6Z6A4tQ7KoO8Rz7WxdLha1NCOVM5Qq7pxdMB0JNQ5kK6ZR3hXjQZx3+4y6S5ye5n6Z3Yr4K3dG8DyPEvYhDw==
sha384-7ThIY5m8UQNw0Xf0YgX1R7yQ+DE0JNkJW5K4yI9B5KvYlZv+KMZ5k7Eb5AI5JjA==
sha512-9W5qgS+0e8Z7Y0HlNQ5kK6ZR3hXjQZx3+4y6S5ye5n6Z3Yr4K3dG8DyPEvYhDw=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
</script>
完整性验证失败的情况
ini
<script
src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"
integrity="sha384-错误的哈希值"
crossorigin="anonymous">
</script>

使用Integrity时,必须设置 crossorigin
-
integrity
校验通常用于跨域资源(如CDN上的JS/CSS) -
浏览器要求:跨域资源的完整性验证必须通过CORS检查
-
如果未设置
crossorigin
,浏览器会:- 以普通方式加载脚本(不带
Origin
头) - 跳过完整性验证 (即使有
integrity
属性也会被忽略) - 控制台警告:
Subresource Integrity: The resource '...' has an integrity attribute, but the request was not CORS-enabled
- 以普通方式加载脚本(不带

referrerpolicy属性
用于控制当浏览器加载脚本时如何发送 HTTP Referer 头部信息。这个属性决定了在请求外部脚本资源时,浏览器会发送多少关于来源页面的信息(即 Referer 头部)给服务器。
referrerpolicy 可以取以下值:
no-referrer
: 完全不发送 Referer 头部no-referrer-when-downgrade (默认值)
: 从 HTTPS 到 HTTP 时不发送 Referer,其他情况发送完整来源origin
: 只发送来源页面的协议、主机和端口origin-when-cross-origin
: 跨域时只发送协议、主机和端口,同域时发送完整路径same-origin
: 仅在同源请求时发送 Refererstrict-origin
: 类似 origin,但从 HTTPS 到 HTTP 时不发送strict-origin-when-cross-origin
: 跨域时只发送协议、主机和端口,同域时发送完整路径,且从 HTTPS 到 HTTP 时不发送unsafe-url
: 总是发送完整 URL 作为 Referer(即使从 HTTPS 到 HTTP)
应用场景
- 隐私保护:限制发送的 Referer 信息
- 安全考虑:防止敏感信息通过 Referer 泄露
- 第三方脚本控制:限制第三方脚本获取来源页面信息
注意事项
- 并非所有浏览器都支持所有 referrerpolicy值
- 默认值通常是 no-referrer-when-downgrade
- 这个属性也可以用于其他资源标签如 <img>, <a>, <iframe> 等
通过合理设置 referrerpolicy,开发者可以更好地控制页面加载外部资源时的隐私和安全级别。
nomodule 与 nonce
nomodule
主要用于在现代浏览器中选择性忽略某些脚本,实现对新旧浏览器的差异化脚本加载策略。
xml
<!-- 现代浏览器执行的模块化代码 -->
<script type="module" src="modern-bundle.js"></script>
<!-- 旧浏览器执行的备用代码(现代浏览器会忽略) -->
<script nomodule src="legacy-bundle.js"></script>
nonce
nonce
(Number Used Once)是 HTML5 中与 内容安全策略(CSP) 配合使用的安全属性,主要用于 安全地允许内联脚本执行,同时防止跨站脚本攻击(XSS)。
核心功能
- 通过一次性随机值,白名单机制允许特定内联脚本执行
- 避免直接使用
unsafe-inline
(完全放开内联脚本,不安全)
安全原理
- 服务器生成随机
nonce
值,写入 HTTP 响应头的 CSP 策略 - 只有匹配
nonce
的<script>
标签会被浏览器执行 - 攻击者无法预测或伪造
nonce
,从而阻止恶意脚本注入
使用场景
- 页面初始化逻辑、A/B 测试代码、统计脚本片段
css
# HTTP 响应头
Content-Security-Policy: script-src 'nonce-ABC123';
xml
<!-- 合法脚本 -->
<script nonce="ABC123">
alert("Hello, CSP!");
</script>
<!-- 非法脚本(会被阻止) -->
<script>
alert("XSS Attack!");
</script>
- 严格 CSP 策略的网站**: 当 CSP 头禁止所有内联脚本时(
script-src 'self'
),通过nonce
放行必要脚本
css
Content-Security-Policy: script-src 'self' 'nonce-ABC123';
- 动态生成 nonce(Node.js)
ini
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use((req, res, next) => {
// 生成随机 nonce
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader(
'Content-Security-Policy',
`script-src 'nonce-${nonce}'`
);
res.locals.nonce = nonce;
next();
});
app.get('/', (req, res) => {
res.send(`
<script nonce="${res.locals.nonce}">
console.log("动态 nonce 生效");
</script>
`);
});
app.listen(3000);
fetchpriority属性
fetchpriority
是 HTML 中的一个较新属性,用于 提示浏览器优先加载某些资源 ,从而优化页面性能。它可以应用于 <script>
、<img>
、<link>
等资源加载标签。
核心功能
- 控制资源加载的优先级:告诉浏览器哪些脚本更重要,应该优先下载。
- 优化关键渲染路径:确保关键脚本(如首屏渲染依赖的代码)优先加载,提升用户体验。
- 减少资源竞争:避免非关键脚本(如广告、分析脚本)阻塞关键资源。
适用场景
- 关键脚本:影响页面渲染或交互的核心 JavaScript(如框架、主业务逻辑)。
- 懒加载脚本:非关键脚本(如埋点代码、延迟加载的模块)可以降低优先级。
- 性能敏感页面:需要精细控制资源加载顺序的 Web 应用。
值 | 说明 |
---|---|
high |
最高优先级,浏览器会优先下载(适用于关键脚本)。 |
low |
低优先级,浏览器会在空闲时加载(适用于非关键脚本)。 |
auto |
默认值,由浏览器自动决定优先级(通常根据位置和类型推断)。 |
写在最后
欢迎到我的个人网站(www.dengzhanyong.com)
关注我的公众号【前端筱园】,不错过每一篇推送
加入【前端筱园交流群】,与大家一起交流,共同进步!