重新认识被低估的script标签,小标签如何成就大作为

在 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-referrerstrict-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属性)的脚本会立即执行
  • deferasync 具有互斥性,现代浏览器会有限使用 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>

作用原理:

  1. 浏览器下载脚本前会先计算其哈希值
  2. 将计算结果与 integrity 提供的哈希值比对
  3. 只有匹配时才会执行脚本

指定多个哈希值

包含多个哈希值(用空格分隔),格式为 alg-hash(如 sha256-xxxsha384-xxxsha512-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,浏览器会:

    1. 以普通方式加载脚本(不带 Origin 头)
    2. 跳过完整性验证 (即使有 integrity 属性也会被忽略)
    3. 控制台警告: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: 仅在同源请求时发送 Referer
  • strict-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)

关注我的公众号【前端筱园】,不错过每一篇推送

加入【前端筱园交流群】,与大家一起交流,共同进步!

相关推荐
Attacking-Coder13 分钟前
前端面试宝典---前端水印
前端
姑苏洛言3 小时前
基于微信公众号小程序的课表管理平台设计与实现
前端·后端
烛阴3 小时前
比UUID更快更小更强大!NanoID唯一ID生成神器全解析
前端·javascript·后端
Alice_hhu3 小时前
ResizeObserver 解决 echarts渲染不出来,内容宽度为 0的问题
前端·javascript·echarts
逃逸线LOF4 小时前
CSS之动画(奔跑的熊、两面反转盒子、3D导航栏、旋转木马)
前端·css
萌萌哒草头将军5 小时前
⚡️Vitest 3.2 发布,测试更高效;🚀Nuxt v4 测试版本发布,焕然一新;🚗Vite7 beta 版发布了
前端
技术小丁5 小时前
使用 HTML + JavaScript 在高德地图上实现物流轨迹跟踪系统
前端·javascript·html
小小小小宇6 小时前
React 并发渲染笔记
前端
stark张宇6 小时前
Web - 面向对象
前端·javascript
yanyu-yaya6 小时前
mac电脑安装 nvm 报错如何解决
java·前端·macos