JavaScript事件機(jī)制之捕獲、冒泡與事件委托詳解
1.什么是JavaScript的事件機(jī)制?
JavaScript的事件機(jī)制一共分為三個部分,分別是事件捕獲,事件冒泡和事件委托。
(1)事件捕獲:指的是事件從根節(jié)點(document節(jié)點)向目標(biāo)事件傳播的過程。
(2)事件冒泡:指的是當(dāng)捕獲到目標(biāo)事件之后從目標(biāo)事件開始向上傳播(傳播至Document節(jié)點)的過程。
(3)事件委托:指的是利用事件冒泡機(jī)制,在對應(yīng)子元素的父元素節(jié)點上綁定事件處理器,避免對多個子元素分別綁定事件監(jiān)聽器。這樣做可以顯著減少內(nèi)存占用和事件處理器的數(shù)量,提升頁面性能,尤其是在子元素數(shù)量很多或動態(tài)生成的場景下,事件委托能降低瀏覽器的事件管理開銷,提高響應(yīng)效率和代碼維護(hù)性。
想要掌握好JavaScript中的事件機(jī)制,首先掌握好事件是如何被捕獲的(事件捕獲階段),捕獲到了事件是怎么觸發(fā)的(事件執(zhí)行階段),事件觸發(fā)之后會怎么樣(事件冒泡階段)。理解這些之后就知道如何利用事件冒泡來進(jìn)行事件委托了。
2.DOM樹
在講述事件機(jī)制之前,首先掌握一個概念,什么是DOM樹。理解了DOM樹可以讓我們更好的理解事件的觸發(fā)和冒泡過程。
什么是DOM樹?
- DOM(文檔對象模型,Document Object Model)是瀏覽器解析HTML后生成的一個樹狀結(jié)構(gòu)。
- 每個HTML標(biāo)簽、文本節(jié)點、屬性都會被表示成DOM樹上的一個節(jié)點。
- 通過DOM樹,JavaScript可以訪問、修改網(wǎng)頁內(nèi)容和結(jié)構(gòu)。
案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="container">
<button id="btn">點擊我</button>
</div>
</body>
</html>DOM樹為:
Document
└── html
└── body
└── div#container
└── button#btn (文本節(jié)點: "點擊我")
3.事件觸發(fā)流程
假設(shè)我們在上面HTML中給按鈕和它的父元素div都綁定點擊事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="container">
<button id="btn">點擊我</button>
</div>
<script>
const container = document.getElementById('container');
const btn = document.getElementById('btn');
container.addEventListener('click', () => {
console.log('容器被點擊');
});
btn.addEventListener('click', () => {
console.log('按鈕被點擊');
});
</script>
</body>
</html>
事件觸發(fā)流程:
- 當(dāng)用戶點擊按鈕時,瀏覽器根據(jù)DOM樹找到事件目標(biāo)(button)。
- 事件先從document開始捕獲,經(jīng)過html、body、div,最后到button(捕獲階段)。明事件捕獲默認(rèn)是不啟用的,需要在
addEventListener第三個參數(shù)設(shè)置為true才會執(zhí)行捕獲階段的監(jiān)聽。否則事件監(jiān)聽默認(rèn)只在目標(biāo)階段和冒泡階段觸發(fā)。 - 事件在button上觸發(fā)(目標(biāo)階段)。
- 事件從button向上冒泡,經(jīng)過div、body、html、document(冒泡階段)。
- 綁定在button和container上的事件都會被觸發(fā),輸出:按鈕被點擊 容器被點擊。
從上述案例我們可以看出,當(dāng)我們在給一個事件綁定了監(jiān)聽器之后,如果該監(jiān)聽器被觸發(fā)了,瀏覽器首先需要找到目標(biāo)元素(捕獲階段),找到目標(biāo)元素之后瀏覽器會觸發(fā)當(dāng)前元素綁定的事件監(jiān)聽器的事件(目標(biāo)階段),當(dāng)執(zhí)行完當(dāng)前的事件之后會繼續(xù)沿著DOM樹向上觸發(fā)(冒泡階段),由于<div id="container">是該按鈕的父元素并且也綁定了事件監(jiān)聽,所以也會觸發(fā),直至傳遞到根節(jié)點。
4. 如何利用事件冒泡機(jī)制(事件委托)
假設(shè)你有一個列表,里面有很多條目,每條目都有一個“刪除”按鈕:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul id="list">
<li>項目1 <button class="delete-btn">刪除</button></li>
<li>項目2 <button class="delete-btn">刪除</button></li>
<li>項目3 <button class="delete-btn">刪除</button></li>
<!-- 可能還有更多項目 -->
</ul>
</body>
</html>
(1)傳統(tǒng)做法(不使用事件委托):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul id="list">
<li>項目1 <button class="delete-btn">刪除</button></li>
<li>項目2 <button class="delete-btn">刪除</button></li>
<li>項目3 <button class="delete-btn">刪除</button></li>
<!-- 可能還有更多項目 -->
</ul>
<script>
const deleteButtons = document.querySelectorAll('.delete-btn');
deleteButtons.forEach(btn => {
btn.addEventListener('click', function() {
const li = this.parentElement;
li.remove();
console.log('刪除了一個項目');
});
});
</script>
</body>
</html>
我們發(fā)現(xiàn),如果想要點擊按鈕就刪除對應(yīng)元素,我們可以給每一個按鈕都綁定事件監(jiān)聽器來達(dá)到效果。這樣對于少部分的事件其實并沒有什么影響,但是如果事件很多的情況下,綁定大量事件監(jiān)聽器會消耗內(nèi)存和性能。而且如果后來還需要動態(tài)添加新的項目,則需要額外綁定事件。
(2)利用事件冒泡機(jī)制(事件委托)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul id="list">
<li>項目1 <button class="delete-btn">刪除</button></li>
<li>項目2 <button class="delete-btn">刪除</button></li>
<li>項目3 <button class="delete-btn">刪除</button></li>
<!-- 可能還有更多項目 -->
</ul>
<script>
const list = document.getElementById('list');
list.addEventListener('click', function(event) {
// 利用事件冒泡,判斷點擊目標(biāo)是否是刪除按鈕
if (event.target && event.target.classList.contains('delete-btn')) {
const li = event.target.parentElement;
li.remove();
console.log('刪除了一個項目(事件委托)');
}
});
</script>
</body>
</html>
從上述案例我們可以發(fā)現(xiàn),我們只綁定了一個事件監(jiān)聽(ul),但是當(dāng)子元素(li)觸發(fā)點擊事件之后,我們并沒有對子元素綁定事件處理,但是也能夠進(jìn)行刪除操作,這是因為子元素的事件會沿著DOM樹向上傳播,當(dāng)傳播到ul節(jié)點的時候觸發(fā)了ul的事件處理機(jī)制。所以進(jìn)行了刪除。
5.如何阻止事件冒泡
當(dāng)我們希望事件只在某個特定元素上響應(yīng),而不影響其父元素或祖先元素時,可以使用 event.stopPropagation() 來阻止事件冒泡。
此外,如果希望不僅阻止事件冒泡,還阻止當(dāng)前元素上后續(xù)的同類型事件監(jiān)聽器執(zhí)行,可以使用 event.stopImmediatePropagation()。
區(qū)別:
event.stopPropagation():阻止事件繼續(xù)冒泡到父元素,但當(dāng)前元素上綁定的其他同類型事件監(jiān)聽器仍會執(zhí)行。event.stopImmediatePropagation():阻止事件冒泡,并且阻止當(dāng)前元素上后續(xù)同類型事件監(jiān)聽器的執(zhí)行。
案例:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>阻止事件冒泡案例</title>
</head>
<body>
<div id="outer" style="padding:20px; border:1px solid #ccc;">
外層容器(點擊會觸發(fā))
<button id="inner">點擊我</button>
</div>
<script>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
outer.addEventListener('click', () => {
console.log('【外層容器】點擊事件觸發(fā)');
});
// 監(jiān)聽器1:使用 stopPropagation()
inner.addEventListener('click', (event) => {
event.stopPropagation(); // 阻止事件冒泡,父元素的點擊事件不會觸發(fā)
console.log('【按鈕監(jiān)聽器1】點擊事件觸發(fā),事件冒泡被阻止,父元素不會收到事件');
});
// 監(jiān)聽器2:同一元素上的另一個監(jiān)聽器
inner.addEventListener('click', () => {
console.log('【按鈕監(jiān)聽器2】點擊事件觸發(fā),仍然執(zhí)行');
});
/*
// 如果改為使用 stopImmediatePropagation(),效果如下:
inner.addEventListener('click', (event) => {
event.stopImmediatePropagation(); // 阻止事件冒泡,且阻止后續(xù)監(jiān)聽器執(zhí)行
console.log('【按鈕監(jiān)聽器1】點擊事件觸發(fā),事件冒泡和后續(xù)監(jiān)聽器執(zhí)行被阻止,父元素不會收到事件');
});
inner.addEventListener('click', () => {
console.log('【按鈕監(jiān)聽器2】不會執(zhí)行');
});
*/
</script>
</body>
</html>
使用 stopPropagation() 時:
點擊按鈕會輸出:
【按鈕監(jiān)聽器1】點擊事件觸發(fā),事件冒泡被阻止,父元素不會收到事件
【按鈕監(jiān)聽器2】點擊事件觸發(fā),仍然執(zhí)行
使用 stopImmediatePropagation() 時:
點擊按鈕只會輸出:
【按鈕監(jiān)聽器1】點擊事件觸發(fā),事件冒泡和后續(xù)監(jiān)聽器執(zhí)行被阻止,父元素不會收到事件
6.總結(jié)
JavaScript事件機(jī)制包括三個主要階段:
- 捕獲階段:事件從document節(jié)點向目標(biāo)元素逐層傳遞,監(jiān)聽器在此階段觸發(fā)需設(shè)置
capture為true。 - 目標(biāo)階段:事件到達(dá)目標(biāo)元素,觸發(fā)綁定在目標(biāo)元素上的事件監(jiān)聽器。
- 冒泡階段:事件從目標(biāo)元素向上傳播到document,觸發(fā)沿途元素綁定的冒泡監(jiān)聽器。
- 捕獲階段:事件從document節(jié)點向目標(biāo)元素逐層傳遞,監(jiān)聽器在此階段觸發(fā)需設(shè)置
事件冒泡機(jī)制允許我們通過事件委托,只在父元素上綁定事件監(jiān)聽器,利用事件冒泡捕獲子元素事件,極大優(yōu)化性能和代碼維護(hù)。
在需要阻止事件傳播時,可使用event.stopPropagation()阻止冒泡,避免父元素響應(yīng)事件;使用event.stopImmediatePropagation()則進(jìn)一步阻止當(dāng)前元素上后續(xù)同類型監(jiān)聽器執(zhí)行。
事件監(jiān)聽器默認(rèn)在目標(biāo)階段和冒泡階段觸發(fā),捕獲階段監(jiān)聽需要顯式開啟
到此這篇關(guān)于JavaScript事件機(jī)制之捕獲、冒泡與事件委托的文章就介紹到這了,更多相關(guān)JS事件捕獲、冒泡與事件委托內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過Javascript創(chuàng)建一個選擇文件的對話框代碼
通過Javascript創(chuàng)建一個選擇文件的對話框代碼,需要的朋友可以參考下2012-06-06
理解Javascript_07_理解instanceof實現(xiàn)原理
在《Javascript類型檢測》一文中講到了用instanceof來用做檢測類型,讓我們來回顧一下2010-10-10
JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧
這篇文章主要介紹了JavaScript深入V8引擎以及編寫優(yōu)化代碼的5個技巧,JavaScript引擎是執(zhí)行 JavaScript 代碼的程序或解釋器。JavaScript引擎可以實現(xiàn)為標(biāo)準(zhǔn)解釋器,或者以某種形式將JavaScript編譯為字節(jié)碼的即時編譯器。,需要的朋友可以參考下2019-06-06
JavaScript中DOM尺寸測量與節(jié)流技巧總結(jié)
在網(wǎng)頁開發(fā)中,合理獲取DOM元素尺寸及使用節(jié)流技術(shù)對高頻事件進(jìn)行優(yōu)化,是確保頁面流暢運行的關(guān)鍵,通過JavaScript可以方便地獲取元素寬高,并應(yīng)用節(jié)流(Throttle)與防抖(Debounce)技術(shù)處理如滾動或窗口調(diào)整等高頻事件,有效減少性能消耗,需要的朋友可以參考下2024-11-11

