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

提示:这个基本流程会受到 asyncdefer 等属性的影响而产生不同的行为。


三、语法与用法

基本语法

代码示例

<!-- 内联脚本 -->
<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。现代浏览器中该属性可以省略。

说明
text/javascript 默认值,标准 JavaScript 脚本
module ES6 模块脚本
importmap 导入映射,用于定义模块标识符到 URL 的映射
text/babel Babel 转译脚本(需配合 Babel 工具使用)
text/x-template 模板文本,浏览器不会执行
application/json JSON 数据块,浏览器不会当作脚本执行
省略 等同于 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 请求,主要影响错误信息的获取。

说明
匿名(不设置值或 anonymous 不发送凭证(cookies、HTTP 认证等)
use-credentials 发送凭证

代码示例

<!-- 不发送凭证的跨域请求 -->
<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.js

7. nomodule 属性

nomodule 属性是一个布尔属性,用于在不支持 ES6 模块的浏览器中执行回退脚本。

代码示例

<!-- 支持模块的浏览器执行此脚本,忽略 nomodule 脚本 -->
<script type="module" src="app.modern.js"></script>

<!-- 不支持模块的浏览器执行此脚本,忽略 module 脚本 -->
<script nomodule src="app.legacy.js"></script>

8. referrerpolicy 属性

referrerpolicy 属性指定获取脚本时发送的 Referer 请求头信息。

说明
no-referrer 不发送 Referer 头
no-referrer-when-downgrade HTTPS 到 HTTP 时不发送(默认)
origin 只发送源信息
origin-when-cross-origin 同源发送完整 URL,跨源只发送源
same-origin 同源时发送,跨源时不发送
strict-origin 安全级别相同才发送源信息
strict-origin-when-cross-origin 同源发完整 URL,安全跨源发源,不安全不发
unsafe-url 始终发送完整 URL

代码示例

<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标签各属性兼容性

属性 Chrome Firefox Safari Edge IE
src 全部 全部 全部 全部 全部
type 全部 全部 全部 全部 全部
type="module" 61+ 60+ 10.1+ 16+ 不支持
type="importmap" 89+ 108+ 16.4+ 89+ 不支持
async 全部 全部 全部 全部 10+
defer 全部 全部 全部 全部 4+(有bug)
crossorigin 25+ 18+ 7+ 12+ 不支持
integrity 45+ 43+ 11.1+ 17+ 不支持
nomodule 61+ 60+ 10.1+ 16+ 不支持
referrerpolicy 70+ 65+ 14+ 79+ 不支持
blocking 105+ 不支持 不支持 105+ 不支持

noscript标签兼容性

标签 Chrome Firefox Safari Edge IE
<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:决定脚本类型,moduleimportmap 是现代开发的重要特性

  • async:适合独立脚本(如分析工具),下载完即执行,顺序不可控

  • defer:适合需要操作 DOM 的脚本,保证按声明顺序执行

  • crossoriginintegrity:保障跨域脚本安全的重要属性

  • nomodule:提供优雅的浏览器兼容性降级方案

  • noscript:为禁用 JavaScript 的用户提供替代内容

总结:掌握这些属性的用法,能够帮助开发者在不同场景下做出正确的选择,构建出性能优异、安全可靠的 Web 应用。

标签: script标签 defer属性 async属性 模块脚本 SRI安全 importmap noscript 脚本加载

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: CSS滤镜:filter属性与视觉效果 - 详细教程与指南 下一篇: JS集成:HTML内联JavaScrip - 详细教程与实战指南

poll相关推荐