歡迎光臨
每天分享高質量文章

JavaScript高階程式設計筆記 事件冒泡和事件捕獲

作者:韓子遲

網址:http://www.cnblogs.com/zichi/p/4713038.html

1、事件冒泡

要理解事件冒泡,就得先知道事件流。事件流描述的是從頁面接收事件的順序,比如如下的程式碼:

click me!

如果在body和div內都註冊了click的事件監聽,之後又點選了div區域,是body先響應還是div先響應?有意思的是,當時的瀏覽器開發團隊IE和Netscape提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape提出的事件流是事件捕獲流。

IE的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點(檔案)。如上程式碼,點選click事件會這樣傳播:div->body->html->document(雖然我沒寫html元素,但是頁面上預設還是會存在的)

現代的所有瀏覽器都支援事件冒泡,但還是有些細微差別。IE5.5以及更早版本中的事件冒泡會跳過元素(從body直接跳到document)。IE9、ff、chrome和safari則將事件一直冒泡到window物件。

2、事件捕獲

Netscape團隊則提出另一種事件流-事件捕獲。事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件,如果仍以上面的程式碼舉例:document->html->body->div。

雖然事件捕獲是Netscape唯一支援的事件流模型,但是IE9、Safari、chrome、opera和ff目前也都支援這種事件流模型。儘管“DOM2級事件”規範要求事件應該從document物件開始傳播,但這些瀏覽器都是從window物件開始捕獲事件的。

因為老版本的瀏覽器不支援事件捕獲,所以我們建議使用事件冒泡。

3、DOM事件流

“DOM2級事件”規定事件流包括三個階段:事件捕獲階段、處於標的階段和事件冒泡階段。還是上面的程式碼作為例子,單擊div元素會按照如下順序觸發事件:document->html->body->div->body->html->document。

在DOM事件流中,實際的標的(div)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件到body就停止了,下一個階段是“處於標的”階段,於是事件在div上發生,併在事件處理中被看成冒泡階段的一部分。然後,冒泡階段發生,事件又傳播迴檔案。但是多數支援DOM事件流的瀏覽器都實現了一種特定的行為:即使“DOM2級事件”規範明確要求捕獲階段不會涉及標的事件,但IE9、safari、chrome、ff和opera9.5及更高版本都會在捕獲階段觸發事件物件上的事件,結果就是有兩個機會在標的物件上面操作。(IE9、opera、ff、chrome和Safari都支援DOM事件流,IE8及更早版本不支援DOM事件流)。

4、事件處理程式

響應某個事件的函式就叫做事件處理程式。

DOM0級的事件處理程式很簡單,onclick就是常用的DOM0級事件處理函式,只會在冒泡階段被處理。

而“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程式的操作:addEventListener()和removeEventListener(),所有DOM節點都包含這兩個方法,並且它們都接受3個引數:要處理的事件名、作為事件處理程式的函式和一個布林值。最後這個布林值引數如果是true,表示在捕獲階段呼叫事件處理程式;如果是false,表示在冒泡階段呼叫。DOM2級方法新增事件處理程式的好處是可以新增多個事件處理程式,會按照新增順序被處理(無論是捕獲還是冒泡)。這也是為什麼DOM0級事件相容各種瀏覽器,我們卻還是要使用DOM2的原因之一。

var div = document.getElementById(‘myDiv’);

div.addEventListener(‘click’, function() {

console.log(this.id);

}, true);

div.addEventListener(‘click’, function() {

console.log(‘hello world’);

}, true);

而IE與DOM不同,它有自己的方法:attachEvent()和detachEvent(),這兩個方法接受相同的兩個引數:事件處理程式名稱和事件處理程式函式。由於IE8以及更早版本只支援事件冒泡,所以透過attachEvent()新增的事件處理程式都會被新增到冒泡階段(所以不需要第三個引數)。

var div = document.getElementById(‘myDiv’);

div.attachEvent(‘onclick’, function() {

console.log(‘hello world’);

});

註意第一個引數是onclick,而非DOM標準的click。在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程式的作用域,在使用DOM0級方法的情況下,事件處理程式會在其所屬元素的作用域內執行,而在使用attachEvent()方法的情況下,事件處理程式在全域性作用域中執行,因此this等於window(這點要特別註意!!!)。attachEvent()也能新增多個事件處理程式,但是事件的執行順序和新增順序相反。

5、跨瀏覽器的事件處理程式

因為瀏覽器之間的差異(其實就是IE大家都懂的),所以需要編寫跨瀏覽器的事件處理程式。

var EventUtil = {

addHandler: function(element, type, handler) {

if (element.addEventListener) { // DOM2

element.addEventListener(type, handler, false);

} else if (element.attachEvent) { // IE

element.attachEvent(‘on’ + type, handler);

} else { // DOM0

element[‘on’ + type] = handler;

}

},

removeHandler: function(element, type, handler) {

if (element.removeEventListener) {

element.removeEventListener(type, handler, false);

} else if (element.detachEvent) {

element.detachEvent(‘on’ + type, handler);

} else {

element[‘on’ + type] = null;

}

}

};

6、事件物件

在觸發DOM上的某個事件時,會產生一個事件物件event,這個物件包含著所有與事件有關的資訊。坑爹的是DOM中的事件物件和IE又有不同的玩法。

先來說說DOM中的:

var div = document.getElementById(‘myDiv’);

div.onclick = function(e) {

console.log(e.type);

};

div.addEventListener(‘click’, function(e) {

console.log(e.type);

}, false);

上面程式碼我們應該都不陌生,分別實現了DOM0級和DOM2級的事件物件。

e有很多的屬性和方法,這裡提幾個常用的。target和currentTarget,target指的是事件的真正標的,而currentTarget指的是當前的標的,正是利用target我們可以做事件代理。

要阻止特定事件的預設行為,我們可以使用preventDefault()方法,例如連結的預設行為就是在被單擊時會導航到其href指定的url,如果你想阻止這個預設行為,那麼透過連結的onclick事件處理程式可以取消它:

var link = document.getElementById(‘myLink’);

link.onclick = function(e) {

e.preventDefault();

};

只有cancelable屬性設定為true的事件,才可以使用preventDefault()來取消其預設行為。

另外,stopPropagation()方法用於立即停止事件在DOM層中的傳播,即取消進一步的事件捕獲或冒泡。

var div = document.getElementById(‘myDiv’);

div.onclick = function(e) {

console.log(‘click!’);

e.stopPropagation();

};

document.body.onclick = function(e) {

console.log(‘hello world’);

};

而IE中的事件物件是這麼用的:

var div = document.getElementById(‘myDiv’);

div.onclick = function() {

var e = window.e;

console.log(e.type);

};

div.attachEvent(‘onclick’, function(e) {

// 也可以透過window.e訪問

console.log(e.type);

});

IE中的event物件也有很多屬性和方法,比如srcElement就是和DOM中的target屬性相同,而returnValue屬性相當於DOM中的preventDefault()方法,它們的作用都是取消給定事件的預設行為。只要將該值設定為false,就可以阻止預設行為。相應地,canceBubble屬性和DOM中的stopPropagation()方法作用相同,因為IE只支援冒泡,所以它只能取消事件冒泡。

跨瀏覽器的事件物件:

var EventUtil = {

getEvent: function(e) {

return e ? e : window.e;

},

getTarget: function(e) {

return e.target || e.srcElement;

},

preventDefault: function(e) {

if (e.preventDefault) {

e.preventDefault();

} else {

e.returnValue = false;

}

},

stopPropagation: function(e) {

if (e.stopPropagation) {

e.stopPropagation()

} else {

e.cancelBubble = true;

}

}

}

7、事件委託

有了以上作為基礎,事件委託應該是很簡單了。什麼是事件委託?對“事件處理程式過多”問題的解決方案就是事件委託。事件委託利用了事件冒泡,只指定一個事件處理程式,就可以管理某一型別的所有事件。例如,click事件會冒泡到document層次,也就是說,我們可以為整個頁面指定一個onclick事件處理程式,而不必給每個可單擊的元素分別新增事件處理程式。

舉個經常舉的例子,比如有如下程式碼:

需要的效果是每點選相應的

  • 選項,alert它裡面的單詞,或許很簡單:
  • var lis = document.getElementsByTagName(‘li’);

    for(var i = 0, len = lis.length; i < len; i++) {

    lis[i].onclick = function() {

    alert(this.innerHTML);

    };

    }

    但是如上程式碼系結了三個事件,我們知道每個事件系結都需要佔用一定的記憶體,更糟糕的是,如果在程式碼執行過程中,動態地又添加了一個li,這時它沒有系結click的事件,我們還需要手動新增!這時,我們就可以用到事件委託技術:

    var f = document.getElementById(‘myLink’);

    f.onclick = function(e) {

    console.log(e.target.innerHTML);

    };

    好吧,就是這麼簡單!

    贊(0)

    分享創造快樂