在HTML中引入JavaScript的三種方式及優(yōu)缺點詳解
核心答案
在 HTML 中引入 JavaScript 有 3 種主要方式:
| 方式 | 語法 | 主要場景 |
|---|---|---|
| 1. 行內(nèi)式 | <div onclick="alert('hi')"> | 極少使用,不推薦 |
| 2. 內(nèi)嵌式 | <script>alert('hi')</script> | 小型腳本、單頁應(yīng)用 |
| 3. 外鏈?zhǔn)?/strong> | <script src="app.js"></script> | 生產(chǎn)環(huán)境首選 |
核心原則:生產(chǎn)環(huán)境優(yōu)先使用外鏈?zhǔn)剑浜?defer 或 async 優(yōu)化加載性能。
深入解析
1. 三種方式詳解
方式一:行內(nèi)式(Inline)
<!-- 直接在 HTML 屬性中寫 JS -->
<button onclick="alert('點擊了!')">點擊我</button>
<a href="javascript:void(0)" rel="external nofollow" onmouseover="console.log('懸停')">鏈接</a>
優(yōu)點:
- 快速測試、簡單直觀
缺點:
- ? HTML 和 JS 強耦合,難以維護
- ? 無法復(fù)用邏輯
- ? 代碼混亂,可讀性差
- ? 存在 XSS 安全風(fēng)險
- ? 無法利用瀏覽器緩存
方式二:內(nèi)嵌式(Internal / Embedded)
<!DOCTYPE html>
<html>
<head>
<script>
// JS 代碼寫在 <script> 標(biāo)簽內(nèi)
function init() {
console.log('頁面初始化');
}
</script>
</head>
<body>
<h1>內(nèi)嵌式示例</h1>
</body>
</html>
優(yōu)點:
- ? 適合單頁應(yīng)用或小型項目
- ? HTML 和 JS 在同一文件,便于調(diào)試
- ? 可以訪問頁面中的所有元素
缺點:
- ? HTML 文件體積變大
- ? 無法被瀏覽器緩存(每次加載 HTML 都要重新加載 JS)
- ? 多個頁面無法共享同一份 JS 代碼
- ? 不符合關(guān)注點分離原則
方式三:外鏈?zhǔn)剑‥xternal)?推薦
<!-- 基礎(chǔ)用法 --> <script src="js/app.js"></script> <!-- 推薦用法:配合 defer --> <script src="js/app.js" defer></script> <!-- 或者 async(取決于場景) --> <script src="js/analytics.js" async></script>
優(yōu)點:
- ? HTML 與 JS 分離,結(jié)構(gòu)清晰
- ? 可復(fù)用:多個頁面共享同一個 JS 文件
- ? 可緩存:瀏覽器緩存 JS 文件,提升加載速度
- ? 便于維護:代碼獨立管理
- ? 支持模塊化:方便團隊協(xié)作
缺點:
- 需要額外的 HTTP 請求(但可通過緩存和打包優(yōu)化)
2.<script>標(biāo)簽的關(guān)鍵屬性
defer和async的區(qū)別
頁面解析流程對比:
無屬性(默認):
HTML解析 → 遇到script → 停止解析 → 下載JS → 執(zhí)行JS → 繼續(xù)解析HTML
↑ 阻塞頁面渲染 ↑
defer:
HTML解析 → 并行下載JS → HTML解析完成 → 按順序執(zhí)行JS → DOMContentLoaded
↓ 不阻塞解析 ↓
async:
HTML解析 → 并行下載JS → 下載完立即執(zhí)行 → 繼續(xù)解析HTML
↓ 執(zhí)行時機不確定 ↓
| 屬性 | 執(zhí)行時機 | 順序保證 | 適用場景 |
|---|---|---|---|
| 無屬性 | 立即執(zhí)行,阻塞解析 | ? 按順序 | 無 |
| defer | HTML 解析完成后,DOMContentLoaded 前 | ? 按順序 | DOM 操作腳本 |
| async | 下載完成后立即執(zhí)行 | ? 無順序保證 | 獨立腳本(如統(tǒng)計、廣告) |
<!-- defer 推薦用法 --> <script src="main.js" defer></script> <script src="utils.js" defer></script> <!-- 保證:utils.js 一定在 main.js 之前執(zhí)行 --> <!-- async 用法 --> <script src="analytics.js" async></script> <script src="ads.js" async></script> <!-- 不保證執(zhí)行順序,誰先下載完誰先執(zhí)行 -->
其他重要屬性
| 屬性 | 作用 | 示例 |
|---|---|---|
type | 指定腳本類型 | type="module"(ES 模塊) |
crossorigin | CORS 配置 | crossorigin="anonymous" |
integrity | SRI(子資源完整性校驗) | integrity="sha384-..." |
nomodule | 不支持模塊的瀏覽器才執(zhí)行 | <script nomodule src="legacy.js"></script> |
3. 底層機制:瀏覽器如何加載和執(zhí)行腳本
┌─────────────────────────────────────────────────────────┐
│ 瀏覽器渲染流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. HTML Parser ──→ 構(gòu)建 DOM 樹 │
│ ↓ │
│ 2. CSS Parser ──→ 構(gòu)建 CSSOM 樹 │
│ ↓ │
│ 3. 合并 ──→ 渲染樹(Render Tree) │
│ ↓ │
│ 4. Layout(布局) │
│ ↓ │
│ 5. Paint(繪制) │
│ │
└─────────────────────────────────────────────────────────┘
遇到 <script> 時:
默認行為:
┌─────────┐
│ 停止解析 │ ← 阻塞 DOM 構(gòu)建
└────┬────┘
↓
┌─────────┐
│ 下載 JS │ ← 如果是外鏈腳本
└────┬────┘
↓
┌─────────┐
│ 執(zhí)行 JS │ ← 阻塞渲染
└────┬────┘
↓
┌─────────┐
│ 繼續(xù)解析 │
└─────────┘
使用 defer/async:
┌─────────┐ ┌─────────┐
│繼續(xù)解析 │ ←→ │并行下載 │ ← 不阻塞
└─────────┘ └─────────┘
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" >
<!-- 預(yù)加載關(guān)鍵腳本 -->
<link rel="preload" href="critical.js" rel="external nofollow" rel="external nofollow" as="script">
</head>
<body>
<!-- 頁面內(nèi)容 -->
<!-- 方案1:現(xiàn)代瀏覽器推薦 -->
<script src="main.js" defer></script>
<script src="app.js" defer></script>
<!-- 方案2:需要立即執(zhí)行的腳本(如 polyfill) -->
<script>
// 同步執(zhí)行的小型腳本
</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. 常見誤區(qū)
? 誤區(qū)1:defer 和 async 功能一樣
? 糾正:defer 保證順序且在 DOMContentLoaded 前執(zhí)行,async 不保證順序
? 誤區(qū)2:把所有 <script> 都放在 <head> 里
? 糾正:傳統(tǒng)放 </body> 前,現(xiàn)代用 defer 可放 head
? 誤區(qū)3:defer 的腳本一定在 DOMContentLoaded 前執(zhí)行
? 糾正:大部分情況是的,但如果腳本很大或網(wǎng)絡(luò)慢,可能在之后
? 誤區(qū)4:多個 async 腳本按書寫順序執(zhí)行
? 糾正:async 腳本按下載完成順序執(zhí)行,順序不可控
代碼示例
示例1:三種引入方式對比
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS 引入方式對比</title>
<!-- 方式1:行內(nèi)式(不推薦) -->
<button onclick="handleClick()">行內(nèi)式按鈕</button>
<!-- 方式2:內(nèi)嵌式 -->
<script>
function handleClick() {
console.log('內(nèi)嵌式函數(shù)被調(diào)用');
}
// 內(nèi)嵌式可以直接操作頁面
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM 加載完成');
});
</script>
<!-- 方式3:外鏈?zhǔn)剑ㄍ扑]) -->
<script src="js/utils.js" defer></script>
</head>
<body>
<h1>三種引入方式</h1>
<!-- 行內(nèi)式的完整示例 -->
<div onmouseover="this.style.background='yellow'"
onmouseout="this.style.background='white'">
鼠標(biāo)懸停變色
</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>頁面標(biāo)題</h1>
<p>內(nèi)容...</p>
<script>
// 同步腳本:阻塞后續(xù)渲染
console.log('1. 同步腳本開始');
// 模擬耗時操作
const start = Date.now();
while (Date.now() - start < 2000) {}
console.log('2. 同步腳本結(jié)束(阻塞了2秒)');
</script>
<p>這行內(nèi)容被延遲顯示了</p>
<!-- defer 腳本 -->
<script src="defer1.js" defer></script>
<script src="defer2.js" defer></script>
<!-- 保證:defer1.js 在 defer2.js 之前執(zhí)行 -->
<!-- async 腳本 -->
<script src="async1.js" async></script>
<script src="async2.js" async></script>
<!-- 不保證:誰先下載完誰先執(zhí)行 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('3. DOMContentLoaded 觸發(fā)');
});
window.addEventListener('load', function() {
console.log('4. 頁面完全加載完成');
});
</script>
</body>
</html>
示例3:現(xiàn)代項目的標(biāo)準(zhǔn)引入方式
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>現(xiàn)代項目</title>
<!-- 預(yù)連接到 CDN -->
<link rel="preconnect" rel="external nofollow" >
<!-- 預(yù)加載關(guān)鍵資源 -->
<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">
<!-- 關(guān)鍵 CSS -->
<link rel="stylesheet" href="critical.css" rel="external nofollow" rel="external nofollow" >
<!-- Polyfill:需要立即執(zhí)行且不依賴 DOM -->
<script>
// 檢測和添加必要的 polyfill
if (!window.Promise) {
document.write('<script src="polyfills/promise.js"><\/script>');
}
</script>
</head>
<body>
<div id="app"></div>
<!-- 主要應(yīng)用腳本:使用 defer -->
<script src="vendors.js" defer></script>
<script src="main.js" defer></script>
<!-- 第三方統(tǒng)計:使用 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:動態(tài)加載腳本
// 動態(tài)創(chuàng)建 script 標(biāo)簽
function loadScript(url, options = {}) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
// 設(shè)置屬性
if (options.async) script.async = true;
if (options.defer) script.defer = true;
if (options.type) script.type = options.type;
// 事件監(jiān)聽
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) {
// 支持,加載現(xiàn)代版本
loadScript('/modern-image-lazy-load.js');
} else {
// 不支持,加載 polyfill
loadScript('/polyfills/intersection-observer.js')
.then(() => loadScript('/legacy-image-lazy-load.js'));
}
面試技巧
面試官可能的追問
"為什么傳統(tǒng)建議把 <script> 放在 </body> 之前?"
- 避免阻塞頁面渲染,讓用戶先看到內(nèi)容
"現(xiàn)在有了 defer,還需要放 </body> 前嗎?"
- 不需要,
defer可放head,效果相同且更早開始下載
"什么情況下用 async?"
- 獨立腳本:統(tǒng)計代碼、廣告腳本、不依賴其他代碼的庫
"多個 defer 腳本的執(zhí)行順序?"
- 按在 HTML 中的出現(xiàn)順序執(zhí)行
"defer 和 DOMContentLoaded 的關(guān)系?"
defer腳本在 DOMContentLoaded 之前執(zhí)行
"什么是腳本阻塞(render blocking)?"
- 解釋瀏覽器解析 HTML 時遇到
<script>停止渲染的機制
如何展示深度理解
談性能優(yōu)化:
- 關(guān)鍵渲染路徑優(yōu)化
- 資源預(yù)加載(preload/prefetch)
- 代碼分割(code splitting)
談實際項目經(jīng)驗:
- 如何處理第三方腳本(如 Google Analytics)
- 如何優(yōu)化首屏加載時間
- 使用過哪些構(gòu)建工具的優(yōu)化
談瀏覽器兼容性:
defer/async的瀏覽器支持情況- 如何為老瀏覽器做降級處理
談安全:
- SRI(Subresource Integrity)
- nonce/CSP(Content Security Policy)
一句話總結(jié)
外鏈?zhǔn)?+ defer 是現(xiàn)代網(wǎng)頁引入 JavaScript 的最佳實踐,它實現(xiàn)了代碼分離、可緩存、不阻塞渲染的完美平衡。
以上就是在HTML中引入JavaScript的三種方式及優(yōu)缺點詳解的詳細內(nèi)容,更多關(guān)于HTML引入JavaScript方式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS獲取月的最后一天與JS得到一個月份最大天數(shù)的實例代碼
本篇文章主要是對JS獲取月的最后一天與JS得到一個月份最大天數(shù)的實例代碼進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12
Javascript實現(xiàn)通過選擇周數(shù)顯示開始日和結(jié)束日的實現(xiàn)代碼
這篇文章主要介紹了Javascript實現(xiàn)通過選擇周數(shù)顯示開始日和結(jié)束日的實例代碼的相關(guān)資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-05-05
根據(jù)當(dāng)前時間在jsp頁面上顯示上午或下午
這篇文章主要介紹了根據(jù)當(dāng)前時間不同在jsp頁面上顯示上午或下午,需要的朋友可以參考下2014-08-08

