事件流的概念不復(fù)雜讨衣,卻很容易誤解方援。同樣没炒,如果你覺(jué)得自己掌握的清楚,可以嘗試猜猜下面的幾個(gè)例子的輸出結(jié)果會(huì)是什么樣犯戏。
事件流的兩種模型
冒泡和捕獲是事件流的兩種模型
- 事件冒泡(event bubbling)是IE的提出的事件流
- 事件捕獲(event capturing)是 Netscape Communicator提出的事件流送火。
其中目前瀏覽器采用的默認(rèn)是冒泡型。
事件流包含三個(gè)階段:
- 事件捕獲階段
- 處于目標(biāo)階段
- 事件冒泡階段先匪。
不論哪種事件模型包含著三個(gè)階段种吸,差異在于在什么階段處理事件。
我們可以在事件的處理程序中呀非,通過(guò)event.eventPhase
來(lái)獲取到處理程序所在的階段:
- value=1 捕獲階段
- value=2 處于目標(biāo)
- value=3 冒泡階段
需要澄清的概念
我們假定DOM元素是層層包裹的洋蔥骨稿,假定DOM結(jié)構(gòu)為:
<body>
<div>
<span>here is span content</span>
</div>
</body>
加入點(diǎn)擊了span元素,那么在捕獲階段事件的傳遞序列為:body -> div姜钳;在處于目標(biāo)階段,事件停留在span形耗;在冒泡階段哥桥,事件的傳遞序列為:div -> body。
我們?cè)谌我庖粋€(gè)DOM上點(diǎn)擊激涤,比如點(diǎn)擊span拟糕,或點(diǎn)擊div但不點(diǎn)擊到span,如果該DOM上有事件處理程序倦踢,event.eventPhase必然為2送滞。
結(jié)論一:直接點(diǎn)擊元素,并觸發(fā)它上面的事件處理程序辱挥,eventPhase一定等于2犁嗅。
我們能得到event.eventPhase不為2的結(jié)果,一定是點(diǎn)擊元素 和 綁定了事件處理程序的不是同一個(gè)元素晤碘。
先來(lái)個(gè)簡(jiǎn)單的例子:
<button id="testBtn""><i>mockicon</i> Click Me</button>
<script>
const btn = document.getElementById('testBtn');
btn.addEventListener('click', function(e){
const evt = e || event;
console.log(evt.eventPhase);
}/*,true*/)
</script>
(1)定義了一個(gè) button褂微,在它上面綁定點(diǎn)擊事件,直接點(diǎn)擊它园爷,事件處理程序獲取到的 event.eventPhase 是 2
(2)按鈕內(nèi)部有一個(gè)<i class="icon"></i>
宠蚂,點(diǎn)擊按鈕中的i
,事件處理程序獲取到的 event.eventPhase 是 3
(3)如果通過(guò)addEventListener指定第三個(gè)參數(shù)為true童社,采用事件捕獲的事件流求厕,點(diǎn)擊按鈕中的i,事件處理程序獲取到的 event.eventPhase 是1
在(2)中之所以輸出為3,是因?yàn)槭录诓东@呀癣、冒泡階段都經(jīng)過(guò)了div美浦,但是在冒泡階段才被處理;(3)則是在捕獲階段就被處理了十艾。
結(jié)論二:在子元素上點(diǎn)擊抵代,觸發(fā)父元素的點(diǎn)擊事件,事件對(duì)象上的eventPhase為3忘嫉;點(diǎn)擊事件用捕獲模型荤牍,則eventPhase為1
務(wù)必記住的一點(diǎn)是,eventPhase為2只屬于直接點(diǎn)擊的元素庆冕。
你完全懂了嗎
拿下面的4個(gè)例子玩一玩康吵,看你是否能理解輸出結(jié)果。如下是頁(yè)面的css和html結(jié)構(gòu)访递。
<style>
div {
border: 3px solid red;
padding: 15px;
width: 500px;
border-radius: 5px;
margin-bottom: 5px;
}
h3 {
border: 2px solid green;
}
pre {
border: 1px solid gray;
padding: 5px;
}
</style>
<div>
<h3></h3>
<pre></pre>
</div>
1. 只在父元素上綁定點(diǎn)擊事件
<div id="c1" onclick="handler(event)">
<h3 id="t1"> 測(cè)試 #1 只在父元素上綁定點(diǎn)擊事件</h3>
<pre>
點(diǎn)擊子元素H3 或 PRE:
DIV clicked, phase = 3
點(diǎn)擊其他空白區(qū)域:
DIV clicked, phase = 2
</pre>
</div>
<script>
function handler(event) {
console.log(event.currentTarget.nodeName + ' clicked, phase = ' + event.eventPhase);
}
</script>
按照結(jié)論二晦嵌,點(diǎn)擊h3或pre,自身沒(méi)有事件處理拷姿,只會(huì)冒泡到div上惭载,從而eventPhase輸出為3。
按照結(jié)論一响巢,點(diǎn)擊div內(nèi)的空白區(qū)域描滔,相當(dāng)于直接點(diǎn)擊div元素,輸出eventPhase為1踪古。
2. 在父含长、子元素上都綁定點(diǎn)擊事件
<div id="c2" onclick="handler(event)">
<h3 id="t2" onclick="handler(event)">測(cè)試 #2 在父、子元素上都綁定點(diǎn)擊事件</h3>
<pre>
點(diǎn)擊子元素H3:
H3 clicked, phase = 2
DIV clicked, phase = 3
點(diǎn)擊子元素PRE
DIV clicked, phase = 3
點(diǎn)擊非子元素的空白區(qū)域:
DIV clicked, phase = 2
</pre>
</div>
<script>
function handler(event) {
console.log(event.currentTarget.nodeName + ' clicked, phase = ' + event.eventPhase);
}
</script>
相對(duì)上一個(gè)例子的唯一變化是伏穆,h3上也綁定了事件處理程序拘泞,這里唯一變化的是點(diǎn)擊h3的時(shí)候的輸出。
結(jié)合結(jié)論一枕扫、二陪腌,在h3點(diǎn)擊時(shí),先觸發(fā)h3的eventPhase為2铡原,然后再冒泡到div上eventPhase為3偷厦。
3. 捕獲方式只在父元素上綁定點(diǎn)擊事件
<div id="c3">
<h3 id="t3"> 測(cè)試 #3 捕獲方式只在父元素上綁定點(diǎn)擊事件</h3>
<pre>
點(diǎn)擊子元素H3:
DIV clicked, phase = 1
點(diǎn)擊非子元素的空白區(qū)域:
DIV clicked, phase = 2
</pre>
</div>
<script>
function handler(event) {
console.log(event.currentTarget.nodeName + ' clicked, phase = ' + event.eventPhase);
}
var c3 = document.getElementById('c3');
c3.addEventListener('click', handler, true);
</script>
代碼跟例子一一樣,只是捕獲的方式從冒泡修改為了捕獲燕刻。
根據(jù)結(jié)論二只泼,點(diǎn)擊h3的時(shí)候,早于事件到達(dá)h3卵洗,就在div被捕獲了请唱,輸出為div eventPhase為1弥咪。
點(diǎn)擊空白區(qū)域,則仍舊滿(mǎn)足結(jié)論一十绑。
自己曾經(jīng)不能理解聚至,雖然點(diǎn)擊的是h3,但是點(diǎn)擊先作用在洋蔥的外層(div)上本橙,為什么eventPhase不會(huì)是2扳躬,這里繼續(xù)澄清:
eventPhase的值,在“真正”被點(diǎn)擊的元素上是2甚亭,之前都是1贷币,之后都是3。
4. 在父亏狰、子元素上綁定捕獲型點(diǎn)擊事件
<div id="c4">
<h3 id="t4"> 測(cè)試 #4 捕獲方式在父役纹、字元素上都綁定點(diǎn)擊事件</h3>
<pre>
點(diǎn)擊子元素H3:
DIV clicked, phase = 1
H3 clicked, phase = 2
點(diǎn)擊非子元素的空白區(qū)域:
DIV clicked, phase = 2
</pre>
</div>
<script>
function handler(event) {
console.log(event.currentTarget.nodeName + ' clicked, phase = ' + event.eventPhase);
}
var c4 = document.getElementById('c4');
var t4 = document.getElementById('t4');
c4.addEventListener('click', handler, true);
t4.addEventListener('click', handler, true);
</script>
根據(jù)結(jié)論二:點(diǎn)擊h3的時(shí)候,會(huì)先在捕獲階段出發(fā)div eventPhase為1的輸出暇唾,然后到達(dá)h3 eventPhase為2的輸出促脉。