DOM事件機制
本文主要介紹DOM事件級別、DOM事件模型、事件流、事件代理和Event物件常見的應用,希望對你們有些幫助和啟發!
本文首發地址為ofollow,noindex">GitHub部落格 ,寫文章不易,請多多支援與關注!
一、DOM事件級別
DOM級別一共可以分為四個級別:DOM0級、DOM1級、DOM2級和DOM3級。而DOM事件分為3個級別:DOM 0級事件處理,DOM 2級事件處理和DOM 3級事件處理 。由於DOM 1級中沒有事件的相關內容,所以沒有DOM 1級事件。
1.DOM 0級事件
el.onclick=function(){}
// 例1 var btn = document.getElementById('btn'); btn.onclick = function(){ alert(this.innerHTML); } 複製程式碼
當希望為同一個元素/標籤繫結多個同類型事件的時候(如給上面的這個btn元素繫結3個點選事件),是不被允許的。DOM0事件繫結,給元素的事件行為繫結方法,這些方法都是在當前元素事件行為的冒泡階段(或者目標階段)執行的 。
2.DOM 2級事件
el.addEventListener(event-name, callback, useCapture)
- event-name: 事件名稱,可以是標準的DOM事件
- callback: 回撥函式,當事件觸發時,函式會被注入一個引數為當前的事件物件 event
- useCapture: 是否以捕獲的方式觸發,預設為true
// 例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。
3.DOM 3級事件
在DOM 2級事件的基礎上添加了更多的事件型別。
- UI事件,當用戶與頁面上的元素互動時觸發,如:load、scroll
- 焦點事件,當元素獲得或失去焦點時觸發,如:blur、focus
- 滑鼠事件,當用戶通過滑鼠在頁面執行操作時觸發如:dblclick、mouseup
- 滾輪事件,當使用滑鼠滾輪或類似裝置時觸發,如:mousewheel
- 文字事件,當在文件中輸入文字時觸發,如:textInput
- 鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發,如:keydown、keypress
- 合成事件,當為IME(輸入法編輯器)輸入字元時觸發,如:compositionstart
- 變動事件,當底層DOM結構發生變化時觸發,如:DOMsubtreeModified
- 同時DOM3級事件也允許使用者自定義一些事件。
二、DOM事件模型和事件流
DOM事件模型分為捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分成三個階段。
(1)捕獲階段:事件從window物件自上而下向目標節點傳播的階段;
(2)目標階段:真正的目標節點正在處理事件的階段;
(3)冒泡階段:事件從目標節點自下而上向window物件傳播的階段。
DOM事件捕獲的具體流程
捕獲是從上到下,事件先從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)。
1.優點
- 減少記憶體消耗,提高效能
假設有一個列表,列表之中有大量的列表項,我們需要在點選每個列表項的時候響應一個事件
// 例4 <ul id="list"> <li>item 1</li> <li>item 2</li> <li>item 3</li> ...... <li>item n</li> </ul> 複製程式碼
如果給每個列表項一一都繫結一個函式,那對於記憶體消耗是非常大的,效率上需要消耗很多效能。藉助事件代理,我們只需要給父容器ul繫結方法即可,這樣不管點選的是哪一個後代元素,都會根據冒泡傳播的傳遞機制,把容器的click行為觸發,然後把對應的方法執行,根據事件源,我們可以知道點選的是誰,從而完成不同的事。
- 動態繫結事件
在很多時候,我們需要通過使用者操作動態的增刪列表項元素,如果一開始給每個子元素繫結事件,那麼在列表發生變化時,就需要重新給新增的元素繫結事件,給即將刪去的元素解綁事件,如果用事件代理就會省去很多這樣麻煩。
2.如何實現
接下來我們來實現上例中父層元素 #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); } }); 複製程式碼
四、Event物件常見的應用
- event. preventDefault()
如果呼叫這個方法,預設事件行為將不再觸發。什麼是預設事件呢?例如表單一點選提交按鈕(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() & event.stopImmediatePropagation()
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繫結事件不會觸發,與此同時按鈕的另一個點選事件也不觸發。
- event.target & event.currentTarget
老實說這兩者的區別,並不好用文字描述,我們先來看個例子:
<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
是事件的真正發出者
。