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