JavaScript?事件流:從"捕獲"到"冒泡"的完整旅程
當(dāng)你點(diǎn)擊頁面上的一個(gè)按鈕時(shí),這個(gè)點(diǎn)擊動(dòng)作并不是瞬間只在按鈕上發(fā)生的。
在瀏覽器的世界里,這個(gè)點(diǎn)擊事件像一陣風(fēng),經(jīng)歷了一場從上到下,再從下到上的完整旅行。
這就是 DOM 事件流(Event Flow)。
1. ?? 什么是事件流?
定義:
事件流描述的是從頁面中接收事件的順序。
在早期的瀏覽器大戰(zhàn)中, Netscape(網(wǎng)景)和 IE 提出了兩種完全相反的事件傳播模型:
- Netscape:主張事件捕獲(Event Capturing),即事件從最外層向內(nèi)層傳播。
- IE:主張事件冒泡(Event Bubbling),即事件從最內(nèi)層向外層傳播。
最終,W3C 制定了標(biāo)準(zhǔn),將兩者結(jié)合:先捕獲,后冒泡。
通俗比喻:
想象你在一個(gè)多層辦公樓里扔了一個(gè)石子(觸發(fā)事件)。
- 捕獲階段:石子從樓頂(
window)一層層往下掉,經(jīng)過每一層的地板。- 目標(biāo)階段:石子落在了某一層的具體房間(目標(biāo)元素)里。
- 冒泡階段:石子落地后產(chǎn)生的回聲或震動(dòng),從那個(gè)房間一層層往上傳播回樓頂。
2. ?? 事件流的三個(gè)階段
當(dāng)一個(gè)事件發(fā)生時(shí),它會(huì)依次經(jīng)歷以下三個(gè)階段:
第一階段:捕獲階段(Capturing Phase)
- 方向:從根節(jié)點(diǎn)(
window/document)向下傳播,直到目標(biāo)元素的父元素。 - 特點(diǎn):此時(shí)事件正在“尋找”目標(biāo)。
- 注意:默認(rèn)情況下,我們注冊的事件監(jiān)聽器不會(huì)在這個(gè)階段觸發(fā)(除非特別指定)。
第二階段:目標(biāo)階段(Target Phase)
- 方向:事件到達(dá)實(shí)際觸發(fā)事件的元素(
event.target)。 - 特點(diǎn):這是事件真正發(fā)生的地方。
第三階段:冒泡階段(Bubbling Phase)
- 方向:從目標(biāo)元素向上傳播,經(jīng)過父元素、祖父元素……直到根節(jié)點(diǎn)。
- 特點(diǎn):這是最常用的階段。大多數(shù)事件監(jiān)聽器默認(rèn)都注冊在這個(gè)階段。
Window
|
Document
|
HTML
|
Body <--- 1. 捕獲階段 (向下)
| <--- 2. 目標(biāo)階段 (到達(dá) div#target)
Div#container <--- 3. 冒泡階段 (向上)
|
Div#target (你點(diǎn)擊了這里)
3. ?? 代碼實(shí)戰(zhàn):如何控制捕獲與冒泡?
在 JavaScript 中,我們使用 addEventListener 來注冊事件。它的第三個(gè)參數(shù)決定了監(jiān)聽器在哪個(gè)階段觸發(fā)。
語法:
element.addEventListener(event, handler, useCapture);
useCapture為true:在捕獲階段觸發(fā)。useCapture為false(默認(rèn)):在冒泡階段觸發(fā)。
?? 實(shí)驗(yàn)代碼
假設(shè)我們有如下 HTML 結(jié)構(gòu):
<div id="grandparent">
爺爺
<div id="parent">
爸爸
<div id="child">兒子</div>
</div>
</div>
我們給每一層都綁定點(diǎn)擊事件,分別測試捕獲和冒泡:
const grandparent = document.getElementById("grandparent");
const parent = document.getElementById("parent");
const child = document.getElementById("child");
function log(msg, phase) {
console.log(`${msg} - ${phase}`);
}
// 1. 注冊捕獲階段監(jiān)聽器 (useCapture = true)
grandparent.addEventListener(
"click",
() => log("Grandparent", "Capture"),
true,
);
parent.addEventListener("click", () => log("Parent", "Capture"), true);
// 2. 注冊冒泡階段監(jiān)聽器 (useCapture = false, 默認(rèn))
parent.addEventListener("click", () => log("Parent", "Bubble"));
child.addEventListener("click", () => log("Child", "Bubble"));
// 3. 點(diǎn)擊 "兒子" (child)?? 輸出結(jié)果
當(dāng)你點(diǎn)擊 <div id="child"> 時(shí),控制臺(tái)輸出順序如下:
Grandparent - Capture(捕獲:從外向內(nèi))Parent - Capture(捕獲:從外向內(nèi))Child - Bubble(目標(biāo):通常視為冒泡的一部分,或者單獨(dú)的目標(biāo)階段)Parent - Bubble(冒泡:從內(nèi)向外)
注意:
grandparent沒有注冊冒泡監(jiān)聽器,所以最后沒有輸出Grandparent - Bubble。
4. ?? 如何阻止事件傳播?
有時(shí)候,我們不希望事件繼續(xù)傳播(例如:點(diǎn)擊彈窗內(nèi)部不希望關(guān)閉彈窗,但點(diǎn)擊背景要關(guān)閉)。
方法一:event.stopPropagation()
作用:停止事件在 DOM 樹中的進(jìn)一步傳播(既阻止后續(xù)的捕獲,也阻止后續(xù)的冒泡)。
child.addEventListener("click", function (event) {
event.stopPropagation();
console.log("Child clicked, stop!");
});
結(jié)果:點(diǎn)擊 child 后,只會(huì)執(zhí)行 child 的回調(diào),parent 和 grandparent 的回調(diào)都不會(huì)執(zhí)行。
方法二:event.stopImmediatePropagation()
作用:不僅阻止事件傳播,還阻止當(dāng)前元素上其他相同事件的監(jiān)聽器執(zhí)行。
child.addEventListener("click", function (event) {
event.stopImmediatePropagation();
console.log("Handler 1");
});
child.addEventListener("click", function () {
console.log("Handler 2"); // 這行永遠(yuǎn)不會(huì)執(zhí)行
});? 誤區(qū):return false
在 jQuery 中,return false 等價(jià)于同時(shí)調(diào)用 stopPropagation() 和 preventDefault()。
但在原生 JS 中,return false 無效,必須顯式調(diào)用上述方法。
5. ?? 實(shí)際應(yīng)用場景:為什么我們需要它們?
場景一:事件委托(利用冒泡)
這是冒泡最著名的應(yīng)用。與其給 1000 個(gè)列表項(xiàng)分別綁定事件,不如給父元素 <ul> 綁定一個(gè)事件,利用冒泡機(jī)制統(tǒng)一處理。
// 利用冒泡,只在 ul 上監(jiān)聽
ul.addEventListener("click", function (event) {
if (event.target.tagName === "LI") {
console.log("Clicked:", event.target.innerText);
}
});場景二:模態(tài)框關(guān)閉邏輯(混合使用)
常見需求:點(diǎn)擊遮罩層關(guān)閉彈窗,點(diǎn)擊彈窗內(nèi)容不關(guān)閉。
<div id="overlay"> <div id="modal">內(nèi)容...</div> </div>
const overlay = document.getElementById("overlay");
const modal = document.getElementById("modal");
// 1. 點(diǎn)擊遮罩層,關(guān)閉彈窗(冒泡階段)
overlay.addEventListener("click", () => {
console.log("Close Modal");
});
// 2. 點(diǎn)擊彈窗內(nèi)部,阻止冒泡,防止觸發(fā) overlay 的點(diǎn)擊事件
modal.addEventListener("click", (event) => {
event.stopPropagation();
console.log("Modal Content Clicked");
});場景三:高級攔截(利用捕獲)
如果你想在一個(gè)事件到達(dá)目標(biāo)之前“攔截”它(例如全局權(quán)限檢查、日志記錄),可以使用捕獲階段。因?yàn)椴东@發(fā)生在冒泡之前,你可以提前終止事件。
// 在捕獲階段攔截所有點(diǎn)擊
document.addEventListener(
"click",
(event) => {
if (event.target.disabled) {
event.stopPropagation(); // 禁止點(diǎn)擊禁用元素
event.preventDefault();
}
},
true,
); // 注意這里是 true
?? 總結(jié)
| 特性 | 事件捕獲 (Capturing) | 事件冒泡 (Bubbling) |
|---|---|---|
| 傳播方向 | 從上到下 (Window -> Target) | 從下到上 (Target -> Window) |
| 觸發(fā)時(shí)機(jī) | 早期,用于攔截 | 晚期,用于處理 |
| 默認(rèn)行為 | 需設(shè)置 useCapture: true | 默認(rèn) (useCapture: false) |
| 主要用途 | 事件攔截、全局監(jiān)聽 | 事件委托、常規(guī)交互 |
?? 博主寄語:
事件流就像水的流動(dòng),既有從天而降的雨(捕獲),也有匯聚成河向上的蒸汽(冒泡)。記住三個(gè)關(guān)鍵點(diǎn):
- 順序:先捕獲,后目標(biāo),再冒泡。
- 委托:利用冒泡實(shí)現(xiàn)高性能的事件代理。
- 阻止:使用
stopPropagation()控制事件傳播范圍。理解了事件流,你就掌握了 DOM 交互的脈搏!
希望這篇文檔能幫你徹底搞懂事件捕獲與冒泡!
到此這篇關(guān)于JavaScript 事件流:從“捕獲”到“冒泡”的完整旅程的文章就介紹到這了,更多相關(guān)js事件流從捕獲到冒泡內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入理解JS的事件綁定、事件流模型
- JS對象與JSON互轉(zhuǎn)換、New Function()、 forEach()、DOM事件流等js開發(fā)基礎(chǔ)小結(jié)
- JavaScript 事件流、事件處理程序及事件對象總結(jié)
- JavaScript事件學(xué)習(xí)小結(jié)(一)事件流
- 詳解JavaScript中的事件流和事件處理程序
- javascript下對于事件、事件流、事件觸發(fā)的順序隨便說說
- Javascript 事件流和事件綁定
- JavaScript 事件捕獲冒泡與捕獲詳情
- javascript事件冒泡,事件捕獲和事件委托詳解
- js事件冒泡與事件捕獲詳解
相關(guān)文章
javascript簡單進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法
這篇文章主要介紹了javascript簡單進(jìn)制轉(zhuǎn)換實(shí)現(xiàn)方法,涉及javascript字符串轉(zhuǎn)換與數(shù)值操作相關(guān)技巧,需要的朋友可以參考下2016-11-11
微信小程序開發(fā)自定義tabBar實(shí)戰(zhàn)案例(定制消息99+小紅心)
一定的需求情況下無法使用小程序原生的tabbar的時(shí)候,需要自行實(shí)現(xiàn)一個(gè)和tabbar功能一模一樣的自制組件,下面這篇文章主要給大家介紹了關(guān)于微信小程序開發(fā)自定義tabBar(定制消息99+小紅心)的相關(guān)資料,需要的朋友可以參考下2022-12-12
在uni-app中自定義輸入框placeholder的樣式的問題示例展示
這篇文章主要介紹了在uni-app中自定義輸入框placeholder的樣式的問題示例展示,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧2025-05-05
微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改文字大小功能【附demo源碼下載】
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改文字大小功能,涉及微信小程序事件綁定及setData動(dòng)態(tài)修改Page頁面data數(shù)據(jù),進(jìn)而控制頁面元素屬性動(dòng)態(tài)改變的相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
es6 for循環(huán)中l(wèi)et和var區(qū)別詳解
這篇文章主要介紹了es6 for循環(huán)中l(wèi)et和var區(qū)別詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
JAVASCRIPT style 中visibility和display之間的區(qū)別
visibility屬性用來確定元素是顯示還是隱藏的,這用visibility="visible|hidden"來表示(visible表示顯示,hidden表示隱藏)。2010-01-01

