捕獲和冒泡
一個(gè)完整的DOM事件流2.0包含三個(gè)階段
- 捕獲階段:事件從Document節(jié)點(diǎn)自上而下向目標(biāo)節(jié)點(diǎn)傳播的階段岭粤,至目標(biāo)元素父元素為捕獲階段
- 目標(biāo)階段:真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段
- 冒泡階段:事件從目標(biāo)節(jié)點(diǎn)自上而下向Document節(jié)點(diǎn)傳播的階段
eg:當(dāng)點(diǎn)擊一個(gè)元素A觸發(fā)click時(shí)缩搅,該事件會(huì)先進(jìn)入捕獲階段醒串,從頂層即document開(kāi)始向下傳播笙瑟,一直到目標(biāo)元素A進(jìn)入目標(biāo)階段含衔,目標(biāo)階段結(jié)束后,事件會(huì)進(jìn)入冒泡階段弄砍,直到document停止原献。
即捕獲階段由外向內(nèi),冒泡階段由內(nèi)向外盐数。如下圖
舉例
<div class="grandpa">我是爺
<div class="father">我是爹
<div class="son">我是兒</div>
</div>
</div>
<script>
document.querySelector('.grandpa').onclick = function () {
console.log('我是爺')
}
document.querySelector('.father').onclick = function (e) {
console.log('我是爹')
}
document.querySelector('.son').onclick = function () {
console.log('我是兒')
}
</script>
當(dāng)點(diǎn)擊son
元素時(shí)棒拂,控制臺(tái)輸出如下
當(dāng)點(diǎn)擊
.son
時(shí),捕獲階段依次經(jīng)過(guò)ducument
/html
/body
/.grandpa
/.father
玫氢;然后觸發(fā).son
綁定事件帚屉,進(jìn)入目標(biāo)階段,執(zhí)行完目標(biāo)階段后漾峡,再?gòu)?code>.father依次進(jìn)入冒泡階段攻旦。
阻止冒泡
點(diǎn)擊son
時(shí),不想觸發(fā)father
和grandpa
綁定的事件生逸,則需要阻止事件冒泡牢屋,修改son
的click事件
document.querySelector('.son').onclick = function ($e) {
// 不考慮兼容 >=ie9且预,直接調(diào)用stopPropagation
// $e.stopPropagation();
// 兼容寫(xiě)法
$e = window.event || $e;
if (document.all) { //只有ie識(shí)別
$e.cancelBubble = true;
} else {
$e.stopPropagation();
}
console.log('我是兒')
}
此時(shí)點(diǎn)擊son
時(shí),只會(huì)輸出【我是兒】烙无,而不會(huì)觸發(fā)father
和grandpa
的點(diǎn)擊事件锋谐。
事件冒泡不僅可以被阻斷,也可以改變觸發(fā)階段皱炉。
注冊(cè)監(jiān)聽(tīng)事件
上面演示的click事件綁定,通過(guò)ele.onclick=fn
進(jìn)行綁定狮鸭,只適合處理簡(jiǎn)單場(chǎng)景合搅,假設(shè)要在統(tǒng)一元素綁定多個(gè)click事件時(shí),后賦值函數(shù)會(huì)覆蓋前面賦值的函數(shù)歧蕉≡植浚可使用ele.addEventListener(type,listener)
綁定多個(gè)事件。(注:<=IE8的瀏覽器需要通過(guò)ele.attachEvent
注冊(cè)監(jiān)聽(tīng)事件)
在之前的js中添加代碼
var father= document.querySelector('.son')
son.addEventListener("click", function(){
console.log("son1")
},false)
father.addEventListener("click", function(){
console.log("son2")
},false)
點(diǎn)擊son
輸出結(jié)果
addEventListener
按照注冊(cè)順序執(zhí)行惯退,通過(guò)ele.onclick=fn
綁定的函數(shù)赌髓,也是按照注冊(cè)順序執(zhí)行
addEventListener
第三個(gè)參數(shù)可以改變事件觸發(fā)階段,默認(rèn)為false催跪,冒泡階段觸發(fā)锁蠕。可以直接賦boolean值懊蒸,也可以是對(duì)象荣倾,修改多個(gè)屬性。
注:IE9-IE11僅支持第一種骑丸,edge兩種都支持
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, opts]);
-
type
表示監(jiān)聽(tīng)事件類(lèi)型的字符串舌仍。 -
listener
當(dāng)所監(jiān)聽(tīng)的事件類(lèi)型觸發(fā)時(shí),會(huì)接收到一個(gè)事件通知通危。listener
必須是一個(gè)實(shí)現(xiàn)了EventListener
接口的對(duì)象铸豁,或者是一個(gè)函數(shù) -
useCapture
可選參數(shù),默認(rèn)為false
菊碟,表示是否在捕獲階段 -
opts
可選參數(shù)节芥,包含三個(gè)參數(shù)
{
capture:false, //表示是否在捕獲階段逆害,捕獲階段觸發(fā)
once:false, //表示是否執(zhí)行一次藏古,設(shè)置為true則只會(huì)執(zhí)行一次
passive:false // 表示是否順從模式
}
- 第三個(gè)參數(shù)為boolean值,若修改該值為true忍燥,事件將會(huì)在捕獲階段觸發(fā)
點(diǎn)擊<div class="grandpa">我是爺 <div class="father">我是爹 <div class="son">我是兒</div> </div> </div> <script> document.querySelector('.grandpa').addEventListener('click', function () { console.log('我是爺') }, false) document.querySelector('.father').addEventListener('click', function () { console.log('我是爹') }, true) // 修改參數(shù) document.querySelector('.son').addEventListener('click', function () { console.log('我是兒') }, false) </script>
son
拧晕,輸出如下
useCapture
father
的綁定監(jiān)聽(tīng)事件,優(yōu)先執(zhí)行了梅垄,也就是在捕獲階段執(zhí)行厂捞,而非在冒泡階段執(zhí)行输玷。 - 第二種參數(shù)為一個(gè)配置對(duì)象,capture和once好理解靡馁,單獨(dú)說(shuō)下passive
一個(gè)很重要的使用場(chǎng)景欲鹏,在移動(dòng)端,通常會(huì)監(jiān)聽(tīng)touchstart
事件臭墨,但是赔嚎,由于瀏覽器不能預(yù)先預(yù)知是否阻攔了默認(rèn)事件,只能預(yù)處理一次監(jiān)聽(tīng)事件胧弛,檢查是否執(zhí)行了e.preventDefault()
尤误,這個(gè)過(guò)程消耗資源,會(huì)導(dǎo)致滾動(dòng)出現(xiàn)卡頓現(xiàn)象结缚。
當(dāng)設(shè)置passive:true
時(shí)损晤,表示該監(jiān)聽(tīng)函數(shù)為順從模式,不會(huì)阻攔默認(rèn)事件红竭,瀏覽器只需要解析opts而非預(yù)處理監(jiān)聽(tīng)事件尤勋,不會(huì)浪費(fèi)資源,進(jìn)而優(yōu)化了卡頓茵宪。當(dāng)設(shè)置passvie:true時(shí)最冰,使用e.preventDefault()
會(huì)失效,且控制臺(tái)會(huì)有錯(cuò)誤提醒
判斷是否支持passive
var passiveSupported = false;
try {
var options = Object.defineProperty({}, "passive", {
get: function() {
passiveSupported = true;
}
});
window.addEventListener("test-passive", null, options);
} catch(err) {}
移除事件監(jiān)聽(tīng)
target.removeEventListener(type, listener[, useCapture]);
target.removeEventListener(type, listener[, opts]);
必備三要素:【類(lèi)型】【監(jiān)聽(tīng)器】【是否捕獲階段觸發(fā)】
移除事件監(jiān)聽(tīng)時(shí)稀火,需要一一對(duì)應(yīng)锌奴,如果userCapture為true,對(duì)應(yīng)的移除時(shí)也必須為true 憾股。因?yàn)檫@個(gè)監(jiān)聽(tīng)器也有可能還注冊(cè)在了冒泡階段鹿蜀,那樣的話,同一個(gè)監(jiān)聽(tīng)器實(shí)際上對(duì)應(yīng)著兩個(gè)監(jiān)聽(tīng)器對(duì)象(通過(guò) getEventListeners() 可看到)
所以服球,設(shè)置passive
和once
并不需要一一對(duì)應(yīng)茴恰,如果沒(méi)有設(shè)置capture,則可以省略第三個(gè)參數(shù)斩熊。