本文主要介紹DOM事件級別、DOM事件模型、事件流、事件代理和Event對象常見的應用,但願對大家有些幫助和啓發!javascript
本文首發地址爲GitHub博客,寫文章不易,請多多支持與關注! html
DOM級別一共能夠分爲四個級別:DOM0級、DOM1級、DOM2級和DOM3級。而DOM事件分爲3個級別:DOM 0級事件處理,DOM 2級事件處理和DOM 3級事件處理。因爲DOM 1級中沒有事件的相關內容,因此沒有DOM 1級事件。java
el.onclick=function(){}node
// 例1
var btn = document.getElementById('btn');
btn.onclick = function(){
alert(this.innerHTML);
}
複製代碼
當但願爲同一個元素/標籤綁定多個同類型事件的時候(如給上面的這個btn元素綁定3個點擊事件),是不被容許的。DOM0事件綁定,給元素的事件行爲綁定方法,這些方法都是在當前元素事件行爲的冒泡階段(或者目標階段)執行的。git
el.addEventListener(event-name, callback, useCapture)github
// 例2
var btn = document.getElementById('btn');
btn.addEventListener("click", test, false);
function test(e){
e = e || window.event;
alert((e.target || e.srcElement).innerHTML);
btn.removeEventListener("click", test)
}
//IE9-:attachEvent()與detachEvent()。
//IE9+/chrom/FF:addEventListener()和removeEventListener()
複製代碼
IE9如下的IE瀏覽器不支持 addEventListener()和removeEventListener(),使用 attachEvent()與detachEvent() 代替,由於IE9如下是不支持事件捕獲的,因此也沒有第三個參數,第一個事件名稱前要加on。瀏覽器
在DOM 2級事件的基礎上添加了更多的事件類型。bash
DOM事件模型分爲捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分紅三個階段。dom
(1)捕獲階段:事件從window對象自上而下向目標節點傳播的階段;編輯器
(2)目標階段:真正的目標節點正在處理事件的階段;
(3)冒泡階段:事件從目標節點自下而上向window對象傳播的階段。
捕獲是從上到下,事件先從window對象,而後再到document(對象),而後是html標籤(經過document.documentElement獲取html標籤),而後是body標籤(經過document.body獲取body標籤),而後按照普通的html結構一層一層往下傳,最後到達目標元素。
而事件冒泡的流程恰好是事件捕獲的逆過程。 接下來咱們看個事件冒泡的例子:
// 例3
<div id="outer">
<div id="inner"></div>
</div>
......
window.onclick = function() {
console.log('window');
};
document.onclick = function() {
console.log('document');
};
document.documentElement.onclick = function() {
console.log('html');
};
document.body.onclick = function() {
console.log('body');
}
outer.onclick = function(ev) {
console.log('outer');
};
inner.onclick = function(ev) {
console.log('inner');
};
複製代碼
正如咱們上面提到的onclick給元素的事件行爲綁定方法都是在當前元素事件行爲的冒泡階段(或者目標階段)執行的。
因爲事件會在冒泡階段向上傳播到父節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫作事件的代理(delegation)。
假設有一個列表,列表之中有大量的列表項,咱們須要在點擊每一個列表項的時候響應一個事件
// 例4
<ul id="list">
<li>item 1</li>
<li>item 2</li>
<li>item 3</li>
......
<li>item n</li>
</ul>
複製代碼
若是給每一個列表項一一都綁定一個函數,那對於內存消耗是很是大的,效率上須要消耗不少性能。藉助事件代理,咱們只須要給父容器ul綁定方法便可,這樣無論點擊的是哪個後代元素,都會根據冒泡傳播的傳遞機制,把容器的click行爲觸發,而後把對應的方法執行,根據事件源,咱們能夠知道點擊的是誰,從而完成不一樣的事。
在不少時候,咱們須要經過用戶操做動態的增刪列表項元素,若是一開始給每一個子元素綁定事件,那麼在列表發生變化時,就須要從新給新增的元素綁定事件,給即將刪去的元素解綁事件,若是用事件代理就會省去不少這樣麻煩。
接下來咱們來實現上例中父層元素 #list 下的 li 元素的事件委託到它的父層元素上:
// 給父層元素綁定事件
document.getElementById('list').addEventListener('click', function (e) {
// 兼容性處理
var event = e || window.event;
var target = event.target || event.srcElement;
// 判斷是否匹配目標元素
if (target.nodeName.toLocaleLowerCase === 'li') {
console.log('the content is: ', target.innerHTML);
}
});
複製代碼
若是調用這個方法,默認事件行爲將再也不觸發。什麼是默認事件呢?例如表單一點擊提交按鈕(submit)跳轉頁面、a標籤默認頁面跳轉或是錨點定位等。
不少時候咱們使用a標籤僅僅是想當作一個普通的按鈕,點擊實現一個功能,不想頁面跳轉,也不想錨點定位。
//方法一:
<a href="javascript:;">連接</a>
複製代碼
也能夠經過JS方法來阻止,給其click事件綁定方法,當咱們點擊A標籤的時候,先觸發click事件,其次纔會執行本身的默認行爲
//方法二:
<a id="test" href="http://www.cnblogs.com">連接</a>
<script>
test.onclick = function(e){
e = e || window.event;
return false;
}
</script>
複製代碼
//方法三:
<a id="test" href="http://www.cnblogs.com">連接</a>
<script>
test.onclick = function(e){
e = e || window.event;
e.preventDefault();
}
</script>
複製代碼
接下來咱們看個例子:輸入框最多隻能輸入六個字符,如何實現?
// 例5
<input type="text" id='tempInp'>
<script>
tempInp.onkeydown = function(ev) {
ev = ev || window.event;
let val = this.value.trim() //trim去除字符串首位空格(不兼容)
// this.value=this.value.replace(/^ +| +$/g,'') 兼容寫法
let len = val.length
if (len >= 6) {
this.value = val.substr(0, 6);
//阻止默認行爲去除特殊按鍵(DELETE\BACK-SPACE\方向鍵...)
let code = ev.which || ev.keyCode;
if (!/^(46|8|37|38|39|40)$/.test(code)) {
ev.preventDefault()
}
}
}
</script>
複製代碼
event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執行。上面提到事件冒泡階段是指事件從目標節點自下而上向window對象傳播的階段。 咱們在例4的inner元素click事件上,添加event.stopPropagation()
這句話後,就阻止了父事件的執行,最後只打印了'inner'。
inner.onclick = function(ev) {
console.log('inner');
ev.stopPropagation();
};
複製代碼
stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件類型的其它監聽器被觸發。而 stopPropagation 只能實現前者的效果。咱們來看個例子:
<body>
<button id="btn">click me to stop propagation</button>
</body>
......
const btn = document.querySelector('#btn');
btn.addEventListener('click', event => {
console.log('btn click 1');
event.stopImmediatePropagation();
});
btn.addEventListener('click', event => {
console.log('btn click 2');
});
document.body.addEventListener('click', () => {
console.log('body click');
});
// btn click 1
複製代碼
如上所示,使用 stopImmediatePropagation後,點擊按鈕時,不只body綁定事件不會觸發,與此同時按鈕的另外一個點擊事件也不觸發。
老實說這二者的區別,並很差用文字描述,咱們先來看個例子:
<div id="a">
<div id="b">
<div id="c">
<div id="d"></div>
</div>
</div>
</div>
<script>
document.getElementById('a').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id
)
})
document.getElementById('b').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id
)
})
document.getElementById('c').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id
)
})
document.getElementById('d').addEventListener('click', function(e) {
console.log(
'target:' + e.target.id + '¤tTarget:' + e.currentTarget.id
)
})
</script>
複製代碼
當咱們點擊最裏層的元素d的時候,會依次輸出:
target:d¤tTarget:d
target:d¤tTarget:c
target:d¤tTarget:b
target:d¤tTarget:a
複製代碼
從輸出中咱們能夠看到,event.target
指向引發觸發事件的元素,而event.currentTarget
則是事件綁定的元素,只有被點擊的那個目標元素的event.target
纔會等於event.currentTarget
。也就是說,event.currentTarget
始終是監聽事件者,而event.target
是事件的真正發出者。