pin_drop当前位置:知识文库 ❯ 图文

元信息:HTML script标签 - 完整教程与代码示例

教程简介

HTML <script> 标签是引入JavaScript代码的核心机制,其属性配置直接影响页面的加载性能和执行顺序。asyncdefer 的选择、type 属性的演变、crossorigin 的安全配置——每一个细节都可能对用户体验产生显著影响。本教程将深入讲解 <script> 标签的所有属性及其对页面性能的影响。


核心概念

script 标签的加载与执行模型

浏览器在解析HTML时遇到 <script> 标签,默认行为是:

  • 暂停HTML解析

  • 下载脚本文件(如果是外部脚本)

  • 执行脚本

  • 恢复HTML解析

这种默认行为会阻塞页面渲染,因此需要通过 asyncdefer 等属性优化加载策略。

script 标签的核心属性

属性 说明
src 外部脚本URL URL字符串
type 脚本MIME类型 text/javascriptmoduleimportmap
async 异步下载并执行 布尔属性
defer 延迟到DOM解析完成后执行 布尔属性
crossorigin CORS模式 anonymoususe-credentials
integrity 子资源完整性校验 SRI哈希值
nomodule 不支持ES模块的浏览器执行 布尔属性
blocking 阻塞渲染提示 render

语法与用法

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 选择指南:

场景 推荐属性 原因
主应用逻辑 defer 需要DOM,需要按顺序执行
分析/追踪脚本 async 不依赖DOM,不依赖其他脚本
广告脚本 async 独立运行,不应阻塞页面
第三方库(依赖顺序) defer 保证执行顺序
A/B测试脚本 async 尽早执行,不依赖DOM
框架/库 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">&lt;script src="app.js"&gt;&lt;/script&gt;</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">&lt;script defer src="app.js"&gt;&lt;/script&gt;</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">&lt;script async src="analytics.js"&gt;&lt;/script&gt;</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">&lt;script type="module" src="app.js"&gt;&lt;/script&gt;</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>&lt;link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin&gt;</pre>
            <p>提前建立与CDN的连接,减少脚本下载延迟。</p>
        </div>

        <div class="config-item">
            <h3>2. 内联关键脚本</h3>
            <pre>&lt;script&gt;
  window.performanceMark = performance.now();
&lt;/script&gt;</pre>
            <p>仅内联必须尽早执行的极少量代码,如性能标记。</p>
        </div>

        <div class="config-item">
            <h3>3. defer - 主应用逻辑</h3>
            <pre>&lt;script defer src="js/vendor.js"&gt;&lt;/script&gt;
&lt;script defer src="js/app.js"&gt;&lt;/script&gt;</pre>
            <p>主逻辑使用defer,保证DOM解析完成后按顺序执行。</p>
        </div>

        <div class="config-item">
            <h3>4. async - 独立脚本</h3>
            <pre>&lt;script async src="analytics.js"&gt;&lt;/script&gt;</pre>
            <p>分析、广告等独立脚本使用async,不阻塞页面。</p>
        </div>

        <div class="config-item">
            <h3>5. SRI + crossorigin</h3>
            <pre>&lt;script defer
  src="https://cdn.../bootstrap.min.js"
  integrity="sha384-..."
  crossorigin="anonymous"&gt;
&lt;/script&gt;</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>

浏览器兼容性

特性 Chrome Firefox Safari Edge IE
<script> 基本支持 全支持 全支持 全支持 全支持 全支持
defer 全支持 全支持 全支持 全支持 10+(有bug)
async 8+ 3.6+ 5.1+ 14+ 10+
type="module" 61+ 60+ 10.1+ 16+ 不支持
nomodule 61+ 60+ 10.1+ 16+ 不支持(降级执行)
type="importmap" 89+ 108+ 16.4+ 89+ 不支持
crossorigin 25+ 18+ 7+ 14+ 不支持
integrity(SRI) 45+ 43+ 不支持 17+ 不支持
blocking 105+ 不支持 不支持 105+ 不支持

注意事项与最佳实践

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 的区别?
特性 type="module" 普通 <script>
默认加载方式 defer 阻塞
严格模式 自动启用 需手动声明
顶层this undefined window
作用域 模块作用域 全局作用域
重复执行 同一模块只执行一次 每次引入都执行
CORS 需要CORS头 不需要
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> 标签,是提升页面加载速度和用户体验的第一步。


标签: script标签 async属性 defer属性 ES模块 性能优化 跨域脚本 SRI校验

本文涉及AI创作

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

list快速访问

上一篇: 元信息:HTML style标签 - 完整教程与代码示例 下一篇: 元信息:HTML noscript标签 - 完整教程与代码示例

poll相关推荐