1.理解事件流
一言以蔽之,事件捕獲是從外層元素到目標元素的過程,事件冒泡是從目標元素到外層元素的過程。如圖:
event flow
html:
<div id="wrapper">
<button id="event">事件處理程序</button>
</div>
javascript:
var wrapper = document.getElementById('wrapper');
var event = document.getElementById('event');
wrapper.addEventListener("click", function(e){
console.log('捕獲階段執行父元素wrapper的事件處理程序');
}, true);
wrapper.addEventListener("click", function(e){
console.log('冒泡階段執行父元素wrapper的事件處理程序');
}, false);
event.addEventListener("click", function(e){
console.log('捕獲階段執行子元素event的事件處理程序');
}, true);
event.addEventListener("click", function(e){
console.log('冒泡階段執行子元素event的事件處理程序');
}, false);
正如前面所說,這段代碼的輸出是:
捕獲階段執行父元素wrapper的事件處理程序
捕獲階段執行子元素event的事件處理程序
冒泡階段執行子元素event的事件處理程序
冒泡階段執行父元素wrapper的事件處理程序
見 Demo。
2.阻止冒泡
應該在那個階段執行元素的事件處理程序呢?
多數情況下,我們希望在觸發一個元素的事件處理程序時,不影響它的父元素。比如:點擊button,并不希望父元素的click事件處理程序被觸發。
解決方法是:在冒泡階段執行事件處理程序,然后阻止冒泡。
wrapper.addEventListener("click", function(e){
console.log('捕獲階段執行父元素wrapper的事件處理程序');
}, true); // 默認為false
wrapper.如何燙染出滿意的頭發?("click", function(e){
console.log('冒泡階段執行父元素wrapper的事件處理程序');
}, false);
event.addEventListener("click", function(e){
console.log('捕獲階段執行子元素event的事件處理程序');
}, true);
event.addEventListener("click", function(e){
var target = e.target;
e.stopPropagation(); // stop bubbling
console.log('冒泡階段執行子元素event的事件處理程序');
}, false);
輸出:
捕獲階段執行父元素wrapper的事件處理程序
捕獲階段執行子元素event的事件處理程序
冒泡階段執行子元素event的事件處理程序
見 Demo。
像這樣綁定一個事件處理程序就是安全的。
3.事件委托
上面的例子是要阻止冒泡,有時候冒泡機制也可以被利用。先看一個問題:
假設要在頁面上放在 10^n(n>=1) 個列表項元素,當我點擊某個元素時,需要輸出點擊的是第幾個。
一般做法是,遍歷時給每個元素綁定點擊事件:
var li = document.getElementsByTagName('li');
for(var i=0; i<li.length; i++){
li[i].setAttribute('i',i+1);
li[i].addEventListener('click', function(e){
var b = this.getAttribute('i');
console.log('這是第' + b + '個<li>元素');
});
}
另一種方法是,可以利用冒泡機制,在父元素只綁定一次點擊事件:
var li = document.getElementsByTagName('li');
for(var i=0; i<li.length; i++){
li[i].setAttribute('i',i+1);
}
var ul = document.getElementById('wrapper');
ul.addEventListener('click', function(e){
if(e.target && e.target.nodeName.toUpperCase() === 'LI'){
var b = e.target.getAttribute('i');
console.log('這是第' + b + '個<li>元素');
}
});
見 Demo。
這種事件委托的方式減少了事件處理程序,也能降低程序的復雜性和出錯概率。