W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
讓我們從一個示例開始。
處理程序(handler)被分配給了 <div>
,但是如果你點擊任何嵌套的標(biāo)簽(例如 <em>
或 <code>
),該處理程序也會運行:
<div onclick="alert('The handler!')">
<em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em>
</div>
這是不是有點奇怪?如果實際上點擊的是 <em>
,為什么在 <div>
上的處理程序會運行?
冒泡(bubbling)原理很簡單。
當(dāng)一個事件發(fā)生在一個元素上,它會首先運行在該元素上的處理程序,然后運行其父元素上的處理程序,然后一直向上到其他祖先上的處理程序。
假設(shè)我們有 3 層嵌套 FORM > DIV > P
,它們各自擁有一個處理程序:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
點擊內(nèi)部的 <p>
會首先運行 onclick
:
<p>
? 上的。<div>
? 上的。<form>
? 上的。document
? 對象。
因此,如果我們點擊 <p>
,那么我們將看到 3 個 alert:p
→ div
→ form
。
這個過程被稱為“冒泡(bubbling)”,因為事件從內(nèi)部元素“冒泡”到所有父級,就像在水里的氣泡一樣。
幾乎所有事件都會冒泡。
這句話中的關(guān)鍵詞是“幾乎”。
例如,
focus
事件不會冒泡。同樣,我們以后還會遇到其他例子。但這仍然是例外,而不是規(guī)則,大多數(shù)事件的確都是冒泡的。
父元素上的處理程序始終可以獲取事件實際發(fā)生位置的詳細(xì)信息。
引發(fā)事件的那個嵌套層級最深的元素被稱為目標(biāo)元素,可以通過 event.target
訪問。
注意與 this
(=event.currentTarget
)之間的區(qū)別:
event.target
? —— 是引發(fā)事件的“目標(biāo)”元素,它在冒泡過程中不會發(fā)生變化。this
? —— 是“當(dāng)前”元素,其中有一個當(dāng)前正在運行的處理程序。例如,如果我們有一個處理程序 form.onclick
,那么它可以“捕獲”表單內(nèi)的所有點擊。無論點擊發(fā)生在哪里,它都會冒泡到 <form>
并運行處理程序。
在 form.onclick
處理程序中:
this
?(=?event.currentTarget
?)是 ?<form>
? 元素,因為處理程序在它上面運行。event.target
? 是表單中實際被點擊的元素。一探究竟:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="example.css">
</head>
<body>
A click shows both <code>event.target</code> and <code>this</code> to compare:
<form id="form">FORM
<div>DIV
<p>P</p>
</div>
</form>
<script src="script.js"></script>
</body>
</html>
form {
background-color: green;
position: relative;
width: 150px;
height: 150px;
text-align: center;
cursor: pointer;
}
div {
background-color: blue;
position: absolute;
top: 25px;
left: 25px;
width: 100px;
height: 100px;
}
p {
background-color: red;
position: absolute;
top: 25px;
left: 25px;
width: 50px;
height: 50px;
line-height: 50px;
margin: 0;
}
body {
line-height: 25px;
font-size: 16px;
}
form.onclick = function(event) {
event.target.style.backgroundColor = 'yellow';
// chrome needs some time to paint yellow
setTimeout(() => {
alert("target = " + event.target.tagName + ", this=" + this.tagName);
event.target.style.backgroundColor = ''
}, 0);
};
event.target
可能會等于 this
—— 當(dāng)點擊事件發(fā)生在 <form>
元素上時,就會發(fā)生這種情況。
冒泡事件從目標(biāo)元素開始向上冒泡。通常,它會一直上升到 <html>
,然后再到 document
對象,有些事件甚至?xí)竭_(dá) window
,它們會調(diào)用路徑上所有的處理程序。
但是任意處理程序都可以決定事件已經(jīng)被完全處理,并停止冒泡。
用于停止冒泡的方法是 event.stopPropagation()
。
例如,如果你點擊 <button>
,這里的 body.onclick
不會工作:
<body onclick="alert(`the bubbling doesn't reach here`)">
<button onclick="event.stopPropagation()">Click me</button>
</body>
event.stopImmediatePropagation()
如果一個元素在一個事件上有多個處理程序,即使其中一個停止冒泡,其他處理程序仍會執(zhí)行。
換句話說,
event.stopPropagation()
停止向上移動,但是當(dāng)前元素上的其他處理程序都會繼續(xù)運行。
有一個
event.stopImmediatePropagation()
方法,可以用于停止冒泡,并阻止當(dāng)前元素上的處理程序運行。使用該方法之后,其他處理程序就不會被執(zhí)行。
不要在沒有需要的情況下停止冒泡!
冒泡很方便。不要在沒有真實需求時阻止它:除非是顯而易見的,并且在架構(gòu)上經(jīng)過深思熟慮的。
有時 event.stopPropagation()
會產(chǎn)生隱藏的陷阱,以后可能會成為問題。
例如:
stopPropagation
?,以便不會觸發(fā)外部菜單。document.addEventListener('click'…)
? 來捕獲所有的點擊。stopPropagation
? 所阻止點擊的區(qū)域。太傷心了,我們有一個“死區(qū)”。通常,沒有真正的必要去阻止冒泡。一項看似需要阻止冒泡的任務(wù),可以通過其他方法解決。其中之一就是使用自定義事件,稍后我們會介紹它們此外,我們還可以將我們的數(shù)據(jù)寫入一個處理程序中的 event
對象,并在另一個處理程序中讀取該數(shù)據(jù),這樣我們就可以向父處理程序傳遞有關(guān)下層處理程序的信息。
事件處理的另一個階段被稱為“捕獲(capturing)”。它很少被用在實際開發(fā)中,但有時是有用的。
DOM 事件標(biāo)準(zhǔn)描述了事件傳播的 3 個階段:
下面是在表格中點擊 <td>
的圖片,摘自規(guī)范:
也就是說:點擊 <td>
,事件首先通過祖先鏈向下到達(dá)元素(捕獲階段),然后到達(dá)目標(biāo)(目標(biāo)階段),最后上升(冒泡階段),在途中調(diào)用處理程序。
之前,我們只討論了冒泡,因為捕獲階段很少被使用。通常我們看不到它。
使用 on<event>
屬性或使用 HTML 特性(attribute)或使用兩個參數(shù)的 addEventListener(event, handler)
添加的處理程序,對捕獲一無所知,它們僅在第二階段和第三階段運行。
為了在捕獲階段捕獲事件,我們需要將處理程序的 capture
選項設(shè)置為 true
:
elem.addEventListener(..., {capture: true})
// 或者,用 {capture: true} 的別名 "true"
elem.addEventListener(..., true)
capture
選項有兩個可能的值:
false
?(默認(rèn)值),則在冒泡階段設(shè)置處理程序。true
?,則在捕獲階段設(shè)置處理程序。請注意,雖然形式上有 3 個階段,但第 2 階段(“目標(biāo)階段”:事件到達(dá)元素)沒有被單獨處理:捕獲階段和冒泡階段的處理程序都在該階段被觸發(fā)。
讓我們來看看捕獲和冒泡:
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
}
</script>
上面這段代碼為文檔中的 每個 元素都設(shè)置了點擊處理程序,以查看哪些元素上的點擊事件處理程序生效了。
如果你點擊了 <p>
,那么順序是:
HTML
? → ?BODY
? → ?FORM
? → ?DIV
?(捕獲階段第一個監(jiān)聽器):P
?(目標(biāo)階段,觸發(fā)兩次,因為我們設(shè)置了兩個監(jiān)聽器:捕獲和冒泡)DIV
? → ?FORM
? → ?BODY
? → ?HTML
?(冒泡階段,第二個監(jiān)聽器)。有一個屬性 event.eventPhase
,它告訴我們捕獲事件的階段數(shù)。但它很少被使用,因為我們通常是從處理程序中了解到它。
要移除處理程序,?
removeEventListener
? 需要同一階段如果我們
addEventListener(..., true)
,那么我們應(yīng)該在removeEventListener(..., true)
中提到同一階段,以正確刪除處理程序。
同一元素的同一階段的監(jiān)聽器按其設(shè)置順序運行
如果我們在同一階段有多個事件處理程序,并通過
addEventListener
分配給了相同的元素,則它們的運行順序與創(chuàng)建順序相同:
elem.addEventListener("click", e => alert(1)); // 會先被觸發(fā) elem.addEventListener("click", e => alert(2));
當(dāng)一個事件發(fā)生時 —— 發(fā)生該事件的嵌套最深的元素被標(biāo)記為“目標(biāo)元素”(?event.target
?)。
event.target
?,并在途中調(diào)用分配了 ?addEventListener(..., true)
? 的處理程序(?true
? 是 ?{capture: true}
? 的一個簡寫形式)。event.target
? 冒泡到根,調(diào)用使用 ?on<event>
?、HTML 特性(attribute)和沒有第三個參數(shù)的,或者第三個參數(shù)為 ?false/{capture:false}
? 的 ?addEventListener
? 分配的處理程序。每個處理程序都可以訪問 event
對象的屬性:
event.target
? —— 引發(fā)事件的層級最深的元素。event.currentTarget
?(=?this
?)—— 處理事件的當(dāng)前元素(具有處理程序的元素)event.eventPhase
? —— 當(dāng)前階段(capturing=1,target=2,bubbling=3)。任何事件處理程序都可以通過調(diào)用 event.stopPropagation()
來停止事件,但不建議這樣做,因為我們不確定是否確實不需要冒泡上來的事件,也許是用于完全不同的事情。
捕獲階段很少使用,通常我們會在冒泡時處理事件。這背后有一個邏輯。
在現(xiàn)實世界中,當(dāng)事故發(fā)生時,當(dāng)?shù)鼐綍紫茸龀龇磻?yīng)。他們最了解發(fā)生這件事的地方。然后,如果需要,上級主管部門再進(jìn)行處理。
事件處理程序也是如此。在特定元素上設(shè)置處理程序的代碼,了解有關(guān)該元素最詳盡的信息。特定于 <td>
的處理程序可能恰好適合于該 <td>
,這個處理程序知道關(guān)于該元素的所有信息。所以該處理程序應(yīng)該首先獲得機(jī)會。然后,它的直接父元素也了解相關(guān)上下文,但了解的內(nèi)容會少一些,以此類推,直到處理一般性概念并運行最后一個處理程序的最頂部的元素為止。
冒泡和捕獲為“事件委托”奠定了基礎(chǔ) —— 一種非常強(qiáng)大的事件處理模式,我們將在下一章中進(jìn)行研究。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: