事件
摘自W3school
JavaScript 使我們有能力建立動态頁面。事件是可以被 JavaScript 偵測到的行為。
網頁中的每個元素都可以産生某些可以觸發 JavaScript 函數的事件。比方說,我們可以在使用者點選某按鈕時産生一個 onClick 事件來觸發某個函數。事件在 HTML 頁面中定義。
事件流
事件流描述的是從頁面中接收事件的順序。不過IE(微軟)提出的是冒泡流;網景提出的是捕獲流。
事件冒泡
IE提出的事件流叫做事件冒泡,即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點,自下而上去觸發事件,可以形象地比喻為把一顆石頭投入水中,泡泡會一直從水底冒出水面。所有現代的浏覽器都支援事件冒泡。下面為一個冒泡執行個體:
<div id="parent">
parent
<div id="child">child</div>
</div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 先彈出c後是p
c.onclick = function(e){alert('c')};
p.onclick = function(){alert('p')};
</script>
事件捕獲
網景公司提出的事件流叫做事件捕獲,和事件冒泡相反,即事件會從最外層開始發生,直到最具體的元素,自上而下去觸發事件。但是老版本的浏覽器是不支援的,是以預設還是冒泡。下面為一個捕獲執行個體:
<div id="parent">
parent
<div id="child">child</div>
</div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 先彈出p後是c
p.addEventListener('click', function(){alert("p")}, true);
c.addEventListener('click', function(){alert("c")}), true;
</script>
DOM事件流模型
又分為DOM0/DOM2/DOM3級事件。
DOM2級事件規定的事件流包含3個階段,事件捕獲階段、處于目标階段、事件冒泡階段。首先發生的事件捕獲為截獲事件提供機會,然後是實際的目标接收事件,最後一個階段是事件冒泡階段,可以在這個階段對事件做出響應。事件模型,如下圖:

由上圖可知:
1. 一個完整的JS事件流是從window開始,最後回到window的一個過程
2. 事件流被分為三個階段(1~5)捕獲過程、(5~6)目标過程、(6~10)冒泡過程
示例:
<div id="parent">
parent
<div id="child">child</div>
</div>
<script>
var p = document.getElementById('parent'),
c = document.getElementById('child');
/*
點選子節點依次的觸發順序為:window捕獲,父節點捕獲,子節點捕獲,子節點冒泡,父節點冒泡,window冒泡
點選父節點依次的觸發順序為:window捕獲,父節點捕獲,父節點冒泡,window冒泡
target/currentTarget/eventPhase 都是event裡面的屬性。
target是真正發生事件的DOM元素,currentTarget是目前事件發生在哪個DOM元素上,eventPhase表示目前所處階段。
eventPhase值為1/2/3,分别表示為:捕獲階段/目标階段/冒泡階段
目标階段指target === currentTarget或者eventPhase === 2的時候。
window沒有nodeName,是以為undefined
*/
c.addEventListener('click', function (e) {
console.log('子節點捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
c.addEventListener('click', function (e) {
console.log('子節點冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, false);
p.addEventListener('click', function (e) {
console.log('父節點捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
p.addEventListener('click', function (e) {
console.log('父節點冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, false);
window.addEventListener('click', function (e) {
console.log('window捕獲', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
}, true);
window.addEventListener('click', function (e) {
console.log('window冒泡', e.target.nodeName, e.currentTarget.nodeName, e.eventPhase)
});
</script>
以上執行結果如下圖:
阻止冒泡
e.stopPropagation()或者e.stopImmediateProgation()或者設定e.cancelBubble = true;均可阻止事件冒泡。
e.cancelBubble = true
适用老牌IE浏覽器,不過現在主流浏覽器也支援,效果同e.stopPropagation();
e.stopPropagation()
可阻止事件的向上繼續傳播,但不會阻止該元素綁定的其他事件的執行;
e.stopImmediateProgation()
做了兩件事,先是阻止綁定在該元素上的其他事件,再阻止其向上傳播。
<div id="parent">
parent
<div id="child">child</div>
</div>
<script>
var p = document.getElementById('parent');
var c = document.getElementById('child');
// 第一種情況:先c後p,預設冒泡
c.onclick = function(e){alert('c')};
p.onclick = function(){alert('p')};
// /*
// 第二種情況:先p後c
// 原理分析:addEventListener(event, function, boolean)屬于DOM2級事件,但有相容性問題;
// 對于方法的相容性檢視,可去can i use 即caniuse.com檢視。
// 第三個參數預設不填(即為false)表示冒泡,填寫true則表示捕獲;
// 它允許給同一個事件注冊多個監聽器,下面第五種情況會涉及到。
// */
// p.addEventListener('click', function(){alert("p")}, true);
// c.addEventListener('click', function(){alert("c")}), true;
// // 第三種情況:隻有c,三種方法都可阻止冒泡
// // c.onclick = function(e){alert('c'); e.cancelBubble = true};
// // c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
// c.onclick = function(e){alert('c'); e.stopPropagation();};
// p.onclick = function(){alert('p')};
// /*
// 第四種情況:先c後p
// 原因分析:onclick屬于DOM0事件,如果同一個元素綁定多個事件,會被後者覆寫
// */
// // c.onclick = function(e){alert('c'); e.cancelBubble = true};
// // c.onclick = function(e){alert('c'); e.stopPropagation();};
// c.onclick = function(e){alert('c'); e.stopImmediatePropagation();};
// c.onclick = function(e){alert('c')};
// p.onclick = function(){alert('p')};
// // 第五種情況:先c後ccc,綁定的後續事件不能被阻止
// // c.addEventListener('click', function(e){alert("c"); e.cancelBubble = true;});
// c.addEventListener('click', function(e){alert("c"); e.stopPropagation();});
// c.addEventListener('click', function(e){alert("ccc")});
// p.addEventListener('click', function(){alert("p")});
// // 第六種情況:隻有c,綁定的後續事件被阻止
// c.addEventListener('click', function(e){alert("c"); e.stopImmediatePropagation();});
// c.addEventListener('click', function(e){alert("ccc")});
// p.addEventListener('click', function(){alert("p")});
</script>
阻止預設行為
所謂的預設行為就比如是:點選a标簽的時候就跳轉;點選表單的送出按鈕的時候就會送出表單等。有時候不希望這些事發生,是以就要阻止預設行為了。
1. 使用w3c的
e.preventDefault()
方法,在IE中使用
e.returnValue = false
。可阻止預設行為,但不會阻止冒泡,要想阻止冒泡再添加相應的阻止冒泡邏輯;
2. 在原生js中也可使用
return false
阻止預設行為,但是該方法在 用addEventListener/attachEvent綁定事件的時候,不生效,必須使用方法1;
3. 在jQuery中使用
return false
即可阻止預設行為又可阻止冒泡。
實作邏輯:
<div id='parent'>
<a href="http://www.baidu.com" id="a">child</a>
</div>
<script>
var p = document.getElementById('parent');
var a = document.getElementById('a');
// 正常情況:先彈a,後p,最後跳轉
a.onclick = function (e) {
alert('彈框關閉後,頁面會跳轉');
};
// // 方法1
// a.onclick = function (e) {
// alert('彈框關閉後,也不會跳轉');
// if(e.preventDefault){
// e.preventDefault();
// }else{
// window.event.returnValue == false;
// }
// // e.stopPropagation()
// };
// // 方法2 不會跳轉
// a.onclick = function (e) {
// alert('彈框關閉後,也不會跳轉');
// e.stopPropagation();
// return false;
// };
// // 方法2 仍會跳轉
// a.addEventListener('click', function (e) {
// alert('彈框關閉後,也不會跳轉了');
// // e.preventDefault();
// return false;
// });
// // 方法3
// $('a').on('click', function () {
// alert('彈框關閉後,也不會跳轉了');
// return false;
// });
p.onclick = function(){alert('p')};
</script>