javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > HTML引入JavaScript方式

在HTML中引入JavaScript的三种方式及优缺点详解

作者:Smilezyl

本文详细介绍了HTML中引入JavaScript的三种方式,包括事件句柄的使用(如点击事件触发警告框)、脚本块的编写位置及其执行顺序,以及引入外部JS文件的实践,需要的朋友可以参考下

核心答案

在 HTML 中引入 JavaScript 有 3 种主要方式

方式语法主要场景
1. 行内式<div onclick="alert('hi')">极少使用,不推荐
2. 内嵌式<script>alert('hi')</script>小型脚本、单页应用
3. 外链式<script src="app.js"></script>生产环境首选

核心原则:生产环境优先使用外链式,配合 deferasync 优化加载性能。

深入解析

1. 三种方式详解

方式一:行内式(Inline)

<!-- 直接在 HTML 属性中写 JS -->
<button onclick="alert('点击了!')">点击我</button>
<a href="javascript:void(0)" rel="external nofollow"  onmouseover="console.log('悬停')">链接</a>

优点:

缺点:

方式二:内嵌式(Internal / Embedded)

<!DOCTYPE html>
<html>
<head>
    <script>
        // JS 代码写在 <script> 标签内
        function init() {
            console.log('页面初始化');
        }
    </script>
</head>
<body>
    <h1>内嵌式示例</h1>
</body>
</html>

优点:

缺点:

方式三:外链式(External)⭐推荐

<!-- 基础用法 -->
<script src="js/app.js"></script>

<!-- 推荐用法:配合 defer -->
<script src="js/app.js" defer></script>

<!-- 或者 async(取决于场景) -->
<script src="js/analytics.js" async></script>

优点:

缺点:

2.<script>标签的关键属性

defer和async的区别

页面解析流程对比:

无属性(默认):
HTML解析 → 遇到script → 停止解析 → 下载JS → 执行JS → 继续解析HTML
                ↑ 阻塞页面渲染 ↑

defer:
HTML解析 → 并行下载JS → HTML解析完成 → 按顺序执行JS → DOMContentLoaded
            ↓ 不阻塞解析 ↓

async:
HTML解析 → 并行下载JS → 下载完立即执行 → 继续解析HTML
            ↓ 执行时机不确定 ↓
属性执行时机顺序保证适用场景
无属性立即执行,阻塞解析✅ 按顺序
deferHTML 解析完成后,DOMContentLoaded✅ 按顺序DOM 操作脚本
async下载完成后立即执行❌ 无顺序保证独立脚本(如统计、广告)
<!-- defer 推荐用法 -->
<script src="main.js" defer></script>
<script src="utils.js" defer></script>
<!-- 保证:utils.js 一定在 main.js 之前执行 -->

<!-- async 用法 -->
<script src="analytics.js" async></script>
<script src="ads.js" async></script>
<!-- 不保证执行顺序,谁先下载完谁先执行 -->

其他重要属性

属性作用示例
type指定脚本类型type="module"(ES 模块)
crossoriginCORS 配置crossorigin="anonymous"
integritySRI(子资源完整性校验)integrity="sha384-..."
nomodule不支持模块的浏览器才执行<script nomodule src="legacy.js"></script>

3. 底层机制:浏览器如何加载和执行脚本

┌─────────────────────────────────────────────────────────┐
│                    浏览器渲染流程                         │
├─────────────────────────────────────────────────────────┤
│                                                          │
│  1. HTML Parser ──→ 构建 DOM 树                          │
│         ↓                                                 │
│  2. CSS Parser ──→ 构建 CSSOM 树                         │
│         ↓                                                 │
│  3. 合并 ──→ 渲染树(Render Tree)                        │
│         ↓                                                 │
│  4. Layout(布局)                                        │
│         ↓                                                 │
│  5. Paint(绘制)                                         │
│                                                          │
└─────────────────────────────────────────────────────────┘

遇到 <script> 时:

默认行为:
┌─────────┐
│ 停止解析 │ ← 阻塞 DOM 构建
└────┬────┘
     ↓
┌─────────┐
│ 下载 JS │ ← 如果是外链脚本
└────┬────┘
     ↓
┌─────────┐
│ 执行 JS │ ← 阻塞渲染
└────┬────┘
     ↓
┌─────────┐
│ 继续解析 │
└─────────┘

使用 defer/async:
┌─────────┐      ┌─────────┐
 │继续解析 │ ←→   │并行下载 │  ← 不阻塞
└─────────┘      └─────────┘

4. 最佳实践

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>最佳实践示例</title>

    <!-- CSS 放在 head 中 -->
    <link rel="stylesheet" href="styles.css" rel="external nofollow" >

    <!-- 预加载关键脚本 -->
    <link rel="preload" href="critical.js" rel="external nofollow"  rel="external nofollow"  as="script">
</head>
<body>
    <!-- 页面内容 -->

    <!-- 方案1:现代浏览器推荐 -->
    <script src="main.js" defer></script>
    <script src="app.js" defer></script>

    <!-- 方案2:需要立即执行的脚本(如 polyfill) -->
    <script>
        // 同步执行的小型脚本
    </script>

    <!-- 方案3:独立第三方脚本 -->
    <script src="analytics.js" async></script>

    <!-- 方案4:ES 模块 -->
    <script type="module" src="module.js"></script>

    <!-- 方案5:模块降级方案 -->
    <script type="module" src="modern.js"></script>
    <script nomodule src="legacy.js"></script>
</body>
</html>

5. 常见误区

误区1deferasync 功能一样

纠正defer 保证顺序且在 DOMContentLoaded 前执行,async 不保证顺序

误区2:把所有 <script> 都放在 <head>

纠正:传统放 </body> 前,现代用 defer 可放 head

误区3defer 的脚本一定在 DOMContentLoaded 前执行

纠正:大部分情况是的,但如果脚本很大或网络慢,可能在之后

误区4:多个 async 脚本按书写顺序执行

纠正async 脚本按下载完成顺序执行,顺序不可控

代码示例

示例1:三种引入方式对比

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>JS 引入方式对比</title>

    <!-- 方式1:行内式(不推荐) -->
    <button onclick="handleClick()">行内式按钮</button>

    <!-- 方式2:内嵌式 -->
    <script>
        function handleClick() {
            console.log('内嵌式函数被调用');
        }

        // 内嵌式可以直接操作页面
        document.addEventListener('DOMContentLoaded', function() {
            console.log('DOM 加载完成');
        });
    </script>

    <!-- 方式3:外链式(推荐) -->
    <script src="js/utils.js" defer></script>
</head>
<body>
    <h1>三种引入方式</h1>

    <!-- 行内式的完整示例 -->
    <div onmouseover="this.style.background='yellow'"
         onmouseout="this.style.background='white'">
        鼠标悬停变色
    </div>
</body>
</html>

示例2:defer vs async 实际效果

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>defer vs async</title>
</head>
<body>
    <h1>页面标题</h1>
    <p>内容...</p>

    <script>
        // 同步脚本:阻塞后续渲染
        console.log('1. 同步脚本开始');
        // 模拟耗时操作
        const start = Date.now();
        while (Date.now() - start < 2000) {}
        console.log('2. 同步脚本结束(阻塞了2秒)');
    </script>

    <p>这行内容被延迟显示了</p>

    <!-- defer 脚本 -->
    <script src="defer1.js" defer></script>
    <script src="defer2.js" defer></script>
    <!-- 保证:defer1.js 在 defer2.js 之前执行 -->

    <!-- async 脚本 -->
    <script src="async1.js" async></script>
    <script src="async2.js" async></script>
    <!-- 不保证:谁先下载完谁先执行 -->

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            console.log('3. DOMContentLoaded 触发');
        });

        window.addEventListener('load', function() {
            console.log('4. 页面完全加载完成');
        });
    </script>
</body>
</html>

示例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>

    <!-- 预连接到 CDN -->
    <link rel="preconnect" href="https://cdn.example.com" rel="external nofollow" >

    <!-- 预加载关键资源 -->
    <link rel="preload" href="critical.css" rel="external nofollow"  rel="external nofollow"  as="style">
    <link rel="preload" href="critical.js" rel="external nofollow"  rel="external nofollow"  as="script">

    <!-- 关键 CSS -->
    <link rel="stylesheet" href="critical.css" rel="external nofollow"  rel="external nofollow" >

    <!-- Polyfill:需要立即执行且不依赖 DOM -->
    <script>
        // 检测和添加必要的 polyfill
        if (!window.Promise) {
            document.write('<script src="polyfills/promise.js"><\/script>');
        }
    </script>
</head>
<body>
    <div id="app"></div>

    <!-- 主要应用脚本:使用 defer -->
    <script src="vendors.js" defer></script>
    <script src="main.js" defer></script>

    <!-- 第三方统计:使用 async -->
    <script src="analytics.js" async></script>

    <!-- ES 模块 + 降级方案 -->
    <script type="module" src="modern-app.js"></script>
    <script nomodule src="legacy-app.js"></script>
</body>
</html>

示例4:动态加载脚本

// 动态创建 script 标签
function loadScript(url, options = {}) {
    return new Promise((resolve, reject) => {
        const script = document.createElement('script');
        script.src = url;

        // 设置属性
        if (options.async) script.async = true;
        if (options.defer) script.defer = true;
        if (options.type) script.type = options.type;

        // 事件监听
        script.onload = () => resolve(script);
        script.onerror = () => reject(new Error(`Failed to load ${url}`));

        document.head.appendChild(script);
    });
}

// 使用示例
async function initApp() {
    try {
        await loadScript('/utils.js', { defer: true });
        await loadScript('/main.js', { defer: true });
        console.log('所有脚本加载完成');
    } catch (error) {
        console.error('脚本加载失败:', error);
    }
}

// 条件加载
if ('IntersectionObserver' in window) {
    // 支持,加载现代版本
    loadScript('/modern-image-lazy-load.js');
} else {
    // 不支持,加载 polyfill
    loadScript('/polyfills/intersection-observer.js')
        .then(() => loadScript('/legacy-image-lazy-load.js'));
}

面试技巧

面试官可能的追问

"为什么传统建议把 <script> 放在 </body> 之前?"

"现在有了 defer,还需要放 </body> 前吗?"

"什么情况下用 async?"

"多个 defer 脚本的执行顺序?"

"deferDOMContentLoaded 的关系?"

"什么是脚本阻塞(render blocking)?"

如何展示深度理解

谈性能优化

谈实际项目经验

谈浏览器兼容性

谈安全

一句话总结

外链式 + defer 是现代网页引入 JavaScript 的最佳实践,它实现了代码分离、可缓存、不阻塞渲染的完美平衡。

以上就是在HTML中引入JavaScript的三种方式及优缺点详解的详细内容,更多关于HTML引入JavaScript方式的资料请关注脚本之家其它相关文章!

阅读全文