HTML依託於JavaScript來實現用戶與WEB網頁之間的動態交互,接收用戶操做並作出相應的反饋,而事件在此間則充當橋樑的重要角色。java
平常開發中,常常會爲某個元素綁定一個事件,編寫相應的業務邏輯,在元素被點擊時執行,並反饋到用戶操做界面。瀏覽器
這個過程當中,事件就像一個偵聽器,當點擊動做發生時,纔會執行對應的程序。這種模式可稱之爲觀察員模式。函數
接下來就講講DOM事件相關知識。性能
事件就是用戶或瀏覽器自身執行的某種動做
經常使用的DOM事件有click/mouseover/mouseout/keyup/keydown
等。優化
事件流描述的是從頁面中接收事件的順序
HTML描述的是一個DOM文檔結構,而事件流所描述的是DOM文檔節點接收事件順序。this
而事件流有兩種事件模式,捕獲/冒泡,二者所描述的事件傳遞順序對立相反。spa
事件冒泡:事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)
規範要求事件冒泡到document
對象,而瀏覽器則會將事件一直冒泡到window
對象。設計
全部瀏覽器都支持事件冒泡(包括IE9如下)。代理
事件捕獲:(與事件冒泡相反)事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件
與冒泡同樣,雖然規定事件應該從document
對象開始傳播,但瀏覽器廣泛都是從window
對象開始捕獲。code
IE9如下不支持事件捕獲
"DOM2級事件"規定事件流包括三個階段,順序進行
- 事件捕獲階段
- 處於目標階段
- 事件冒泡階段
TIPS: 實際的目標元素在捕獲階段不會接收到事件,在處於目標階段時接收事件發生處理,並被當作是冒泡階段的一部分。
儘管"DOM2級事件"規範明確要求捕獲階段不會涉及事件目標,但瀏覽器會在捕獲階段觸發事件對象上的事件。
響應某個事件的函數方法,咱們稱之爲事件處理程序(或事件偵聽器)
window.onclick = function() { //... } // 這裏的function(){}就是事件處理程序
HTML中元素支持的事件,可使用一個同名的HTML特性來指定,而這個特性的值就是js能執行的代碼或表達式。寫法上能夠看出相似HTML中id/type/class
等屬性的寫法,都是on+'...'
缺點:HTML是結構層(顯示層),而JavaScript是行爲層(業務層)。在顯示層上去編寫業務邏輯代碼處理,會使得HTML與JavaScript代碼耦合過於緊密,很差維護。
DOM級別一共能夠分爲四個級別:DOM0級、DOM1級、DOM2級和DOM3級。
而DOM事件分爲3個級別:DOM 0級事件處理程序,DOM 2級事件處理程序和DOM 3級事件處理程序。DOM 1級中沒有規範事件的相關內容,因此沒有DOM 1級事件處理。
每一個元素(HTML元素)都有本身的事件處理程序屬性,屬性名一般以on開頭,例如onclick/onmouseover
。爲這個屬性的值設置一個函數,就能夠指定事件處理程序。而將其屬性值賦值爲null,則完成解綁。(同個元素沒法綁定多個同名事件)
var myBtn = document.getElementById('myBtn'); // 爲myBtn綁定事件處理程序, 只能綁定一個 myBtn.onclick = function() { alert('Hello world!'); } // 解綁 myBtn.onclick = null;
"DOM2級事件"定義了兩個方法,addEventListener()/removeEventListener()
,用於爲元素綁定和解綁事件。
(可綁定多個事件,區別於DOM0級/HTML僅能綁定一個)。
el.addEventListener(eventName, callBack, useCapture)
var myBtn = document.getElementById('myBtn'); var handleClick = function() { alert('Hello world!'); } // 綁定事件處理程序 myBtn.addEventListener('click', handleClick, false); // 解綁 myBtn.removeEventListener('click', handleClick);
TIPS:DOM2級事件處理程序,解綁時function
必須與傳入addEventListener
相同
// 綁定 myBtn.addEventListener('click', function() { // 匿名函數 }); // 解綁 myBtn.removeEventListener('click',function() { // 匿名函數 }); // add/remove 分別綁定了兩個匿名函數(函數爲引用類型),因此兩個函數並不相同,因此沒法成功解綁
TIPS:綁定多個事件處理程序時,執行順序按綁定順序執行
myBtn.addEventListener('click', function() { // step1... }) myBtn.addEventListener('click', function() { // step2... }) // 執行順序:step1 -> step2
瀏覽器支持狀況:IE9如下不支持DOM2級事件處理程序
IE9如下不支持DOM2級事件,但IE提供了與DOM2級事件相似的兩個方法,attachEvent()/detachEvent
,IE9如下不支持事件捕獲,因此attachEvent
僅支持冒泡階段觸發,只接收兩個參數(eventName, function)。
// 綁定 myBtn.attachEvent('onclick', handleClick); // 解綁 myBtn.detachEvent('onclick', handleClick);
TIPS:
function
必須與傳入attachEvent
相同,這點與DOM2級事件相同attachEvent
事件處理在全局,this
指向window
myBtn.attachEvent('click', function() { // step1... }) myBtn.attachEvent('click', function() { // step2... }) // 執行順序:step2 -> step1
event.preventDefault()
阻止默認事件
event.stopPropagation()
阻止事件流發生傳遞(冒泡/捕獲)
event.stopImmediatePropagation()
阻止剩餘事件處理函數的執行,並阻止當前事件在事件流上傳遞
event.currentTarget
當前綁定事件的元素
event.target
當前觸發事件的元素
同個元素綁定多個同名事件時,stopImmediatePropagation
不只阻止了冒泡,並且會阻止後續事件的執行,能夠理解爲增強版的stopPropagation
myBtn.addEventListener('click', function(event) { // step1; event.stopImmediatePropagation(); }) myBtn.addEventListener('click', function(event) { // step2; // 我被stopImmediatePropagation阻止掉了!!! })
事件處理程序內部,this等於currentTarget(當前綁定事件的元素),而target(當前觸發事件的元素)
// currentTarget == target myBtn.addEventListener('click', function(event) { event.target == event.currentTarget; // true -> myBtn }) // currentTarget != target 捕獲/冒泡 document.body.addEventListener('click', function(event){ event.target == event.currentTarget; // false // event.target -> myBtn // event.currentTarget -> body })
WEB網頁是運行在瀏覽器客戶端的,而計算機分配給瀏覽器的內存及CPU佔用是有限制的。雖然說瀏覽器引擎不斷地發展優化,可是內存佔用多了, 性能難免會損耗。
爲元素指定事件綁定程序,事實上是賦值了一個函數方法,而函數在javaScript中是一種引用類型的數據格式,既然是數據那就須要用到內存儲存。函數建立多了,消耗掉內存。
爲元素指定事件綁定程序,首先須要對DOM進行查詢,找出要綁定事件的元素。而這也會形成DOM元素的訪問次數增長。DOM的操做一直是網頁性能的一個優化點。
瞭解完事件綁定帶來內存跟性能的原理,咱們來看一個例子,例如咱們有一個ul>li
的列表,要監聽每個li
的點擊事件,並觸發事件處理程序。
單獨綁定的話,10個li
就要對DOM元素查詢10次,建立的匿名函數就有10個(固然能夠共同建立同個函數引用),若是還有20個,30個,100個,那麼這種爲每一個li
元素單獨綁定事件的方法,絕對不是最優解。
這就引出下面的優化方案:"事件委託"。
對"事件處理程序綁定過多"的問題,最好的解決方案就是"事件委託"。它的原理是利用了事件流的"冒泡"機制,事件目標元素會把事件向上層傳遞,直到document
(瀏覽器會傳到window
),因此父級節點是能夠接收子節點的事件傳遞。
以剛剛ul>li
的例子,li
有不少個, 但它們有一個共同的父節點ul
。li
的點擊事件會冒泡到ul
,所以咱們能夠在ul
上綁定一個事件處理程序,處理全部li
的點擊事件,而後經過event.target
能夠肯定觸發事件的元素。
var ulParent = document.getElementById('parent'); ulParent.addEventListener('click', function(event) { var taget = event.target; })
經過"事件委託"減小了DOM元素的查詢,以及多個函數的內存佔用,並且還有一個好處,當咱們的li
是動態的,增長和移除時,都無需再作綁定和解綁事件操做,由於它都會冒泡到父級節點。
文檔中移除了綁定了事件的DOM元素,如innerHTML/removeChild()/replaceChild()
等能夠對DOM進行替換,而移除的DOM元素原先所綁定的事件處理程序,並不能有效被瀏覽器垃圾回收,因此佔用一直存在。
因此建議在移除某個DOM元素時,若是其綁定了事件處理程序,需手動解除綁定,釋放內存。
除了爲元素綁定支持的事件之外,咱們還能夠經過Event/CustomEvent
來建立開發者自定義事件。
二者不一樣的是CustomEvent
可傳遞一個Object
對象來傳輸數據。
// Event var eve = new Event('custome'); // CustomeEvent 可傳參數 var eve = new CustomeEvent('custome', { detail: { name: 'KenTsang', age: 28 } }); // 爲DOM元素添加事件監聽 ele.addEventListener('custome', function(event) { console.log(event.detail); }) // 觸發ele綁定的自定義事件 ele.dispatch(eve);
事件這塊還剩下一部分知識點,後續文章會再就模擬事件這塊知識點進行拆分詳解。
天冷了,更文不易,望你們多多點贊。
做者:以樂之名本文原創,有不當的地方歡迎指出。轉載請指明出處。