JS 筆記系列 03_活用 JavaScript DOM:網頁互動的關鍵
距離 JS 筆記第三章又過好久,終於要來寫3.5了。這次玩玩看事件冒泡 (Event Bubbling)、事件代表 (Event Delegation) 、 事件傳遞 (Propagation) 、停止傳遞(stopPropagation)。可以直接看參考影片:
JavaScript 事件傳遞 (Event Propagation)JS 筆記 - 認識 DOM 文件物件模型 小小補充後面會提到 DOM (Document Object Model),簡而言之就是每個節點(標題、內文...)都可以獨立加監聽事件的感覺。
📌 基本概念一:事件傳遞(Event Propagation)
事件傳遞是指瀏覽器在「觸發某個事件」時,它的訊息是如何沿著 DOM 結構流動。這個流程包含三個階段:
- 捕獲階段(Capturing Phase):從外往內找目標元素。
- 目標階段(Target Phase):事件到達你實際點擊的元素。
- 冒泡階段(Bubbling Phase):從目標元素往上冒到文件根節點。

原圖來自:TimCodingBlog
🧪 範例:觀察事件傳遞流程
<div id="outer">
<div id="middle">
<button id="inner">Click Me</button>
</div>
</div>
document.getElementById('outer').addEventListener('click', () => {
console.log('Outer - Capturing');
}, true); // 設為 true:在捕獲階段觸發
document.getElementById('middle').addEventListener('click', () => {
console.log('Middle - Bubbling');
}); // 預設 false:在冒泡階段觸發
document.getElementById('inner').addEventListener('click', () => {
console.log('Inner clicked');
});
✅ 預期結果(按下 button 時):

📘 補充:true
表示捕獲階段處理;沒設就是冒泡。
再補充:如果直接貼我上面的程式碼,會無法把 JS 跟 HTML 連在一起。上面的程式碼只是方便理解內容,記得要有 從外部導入 JS 的概念,加上
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<title>Event Demo</title>
</head>
<body>
<div id="outer">
<div id="middle">
<button id="inner">Click Me</button>
</div>
</div>
<!-- JavaScript 引入 -->
<script src="script.js"></script>
</body>
</html>
為了更方便理解,我請 Copilot 幫我寫一個有顏色的互動版
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<title>事件傳遞示範</title>
<style>
#outer {
background-color: #f94144; /* 紅色 */
padding: 30px;
}
#middle {
background-color: #f9c74f; /* 黃色 */
padding: 30px;
}
#inner {
background-color: #43aa8b; /* 青綠色 */
padding: 20px;
border: none;
color: white;
font-weight: bold;
cursor: pointer;
}
body {
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<h2>事件傳遞示範:捕獲 vs 冒泡</h2>
<p>點擊按鈕,開發者工具(F12)會顯示事件順序。</p>
<div id="outer">
Outer 區域
<div id="middle">
Middle 區域
<button id="inner">Click Me</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
document.getElementById('outer').addEventListener('click', () => {
console.log('🔴 Outer - Capturing');
}, true); // 捕獲階段
document.getElementById('middle').addEventListener('click', () => {
console.log('🟡 Middle - Bubbling');
}); // 預設:冒泡
document.getElementById('inner').addEventListener('click', () => {
console.log('🟢 Inner clicked');
});

如果當你點擊「Click Me」:
👣 事件流程:
- Outer 捕獲階段先觸發(紅色區塊)
- 再來是 Inner 本身的事件(青綠色按鈕)
- 然後再經過 Middle 的冒泡階段(黃色區塊)
對了,如果有時間可以試試每個事件觸發時改變按鈕或區塊顏色
document.getElementById('outer').addEventListener('click', () => {
console.log('🔶 Outer - Capturing');
document.getElementById('outer').style.backgroundColor = 'yellow';
}, true); // 捕獲階段 改背景色
📌 基本概念二:事件委派/代表(Event Delegation)
事件代表指的是「把事件綁在父層容器」,而不是每個小元素都綁一次。這樣做有幾個好處:
- 減少記憶體負擔
- 新增的元素也能被監聽
- 適用於動態列表、商品清單
🧪 範例:用事件代表監聽 button 點擊
<div id="container">
<button>Item 1</button>
<button>Item 2</button>
<button>Item 3</button>
</div>
document.getElementById('container').addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
console.log(`${e.target.textContent} clicked`);
}
});
✅ 預期結果(點 Item 1):記得開 f12 看
Item 1 clicked
📘 補充:透過 e.target
判斷點擊的是哪個子元素。
📌 基本概念三:停止傳遞(stopPropagation)
當你只想讓事件處理在某一層結束,不往外冒泡(或捕獲),就用 e.stopPropagation()
。
🧪 範例:阻止父層 console 印出
<div id="parent">
<button id="child">Click</button>
</div>
document.getElementById('parent').addEventListener('click', () => {
console.log('Parent clicked');
});
document.getElementById('child').addEventListener('click', (e) => {
console.log('Child clicked');
e.stopPropagation(); // 停止冒泡,不讓 parent 執行
});
✅ 預期結果(按下 button):
Child clicked
📘 補充:如果沒加 stopPropagation()
,則會是:
Child clicked Parent clicked
🏢 商業環境實際應用案例
✨ 情境:動態建立商品按鈕(不斷新增)
關鍵程式碼:
因為 ul 是「無序清單」(unordered list),會在網頁上占一個「空白」的位置。在練習的過程中,還是要加入一些設計(顏色)等等才看的到。
<ul id="product-list">
<!-- 商品按鈕動態產生 -->
</ul>
document.getElementById('product-list').addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
console.log(`商品 ${e.target.dataset.id} 被點擊`);
}
});
// 假設動態新增商品
function addProduct(id) {
const btn = document.createElement('button');
btn.textContent = `商品 ${id}`;
btn.dataset.id = id;
document.getElementById('product-list').appendChild(btn);
}
addProduct(101);
addProduct(102);
----完整網頁程式碼----
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<title>商品動態事件委派</title>
<style>
body {
font-family: Arial, sans-serif;
}
#product-list {
padding: 20px;
border: 1px solid #ccc;
background-color: #f1f1f1;
}
button {
margin: 5px;
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<h2>商品列表</h2>
<p>點擊任意按鈕看看 Console 輸出</p>
<ul id="product-list">
<!-- 商品按鈕動態產生 -->
</ul>
<script src="script.js"></script>
</body>
</html>
// 👂 綁定事件監聽器在父層 ul,使用事件委派處理動態按鈕
document.getElementById('product-list').addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') {
console.log(`商品 ${e.target.dataset.id} 被點擊`);
}
});
// 🪄 建立動態商品按鈕
function addProduct(id) {
const btn = document.createElement('button');
btn.textContent = `商品 ${id}`; // 顯示在按鈕上的文字
btn.dataset.id = id; // 使用 dataset 傳遞商品編號
document.getElementById('product-list').appendChild(btn);
}
// ➕ 新增商品
addProduct(101);
addProduct(102);
addProduct(103);
✅ 點擊商品結果:

總結 Event Delegation :只監聽一次,新增幾百個商品都沒問題。
補充:這裡個語法是e.target.tagName。
(確定使用者點的目標後再做動作)
e
是事件物件,event 的縮寫,當你觸發 click
時,瀏覽器會自動幫你傳入一個包含這次點擊的細節的物件。
把這段想成是一位警衛:
- 他站在
#product-list
門口觀察(事件監聽器) - 有人點進來,他會說:「你是誰?」(
e.target
) - 只有當你是「BUTTON」他才會做出回應(
if
條件) - 然後記錄你的身分(
data-id
)在日誌裡(console.log
)
以上就是 JS 3.5 的學習筆記,謝謝指教!