讀書筆記(05) - 事件 - JavaScript高級程序設計

coding

HTML依託於JavaScript來實現用戶與WEB網頁之間的動態交互,接收用戶操做並作出相應的反饋,而事件在此間則充當橋樑的重要角色。java

平常開發中,常常會爲某個元素綁定一個事件,編寫相應的業務邏輯,在元素被點擊時執行,並反饋到用戶操做界面。瀏覽器

這個過程當中,事件就像一個偵聽器,當點擊動做發生時,纔會執行對應的程序。這種模式可稱之爲觀察員模式。函數

接下來就講講DOM事件相關知識。性能

何爲事件

事件就是用戶或瀏覽器自身執行的某種動做

經常使用的DOM事件有click/mouseover/mouseout/keyup/keydown等。優化

事件流

事件流描述的是從頁面中接收事件的順序

HTML描述的是一個DOM文檔結構,而事件流所描述的是DOM文檔節點接收事件順序。this

而事件流有兩種事件模式,捕獲/冒泡,二者所描述的事件傳遞順序對立相反。spa

事件模式:捕獲與冒泡

冒泡
事件冒泡:事件開始時由最具體的元素(文檔中嵌套層次最深的那個節點)接收,而後逐級向上傳播到較爲不具體的節點(文檔)

規範要求事件冒泡到document對象,而瀏覽器則會將事件一直冒泡到window對象。設計

全部瀏覽器都支持事件冒泡(包括IE9如下)。代理

事件冒泡

捕獲
事件捕獲:(與事件冒泡相反)事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件

與冒泡同樣,雖然規定事件應該從document對象開始傳播,但瀏覽器廣泛都是從window對象開始捕獲。code

IE9如下不支持事件捕獲

事件捕獲

DOM 事件流

"DOM2級事件"規定事件流包括三個階段,順序進行

  1. 事件捕獲階段
  2. 處於目標階段
  3. 事件冒泡階段

TIPS: 實際的目標元素在捕獲階段不會接收到事件,在處於目標階段時接收事件發生處理,並被當作是冒泡階段的一部分。

儘管"DOM2級事件"規範明確要求捕獲階段不會涉及事件目標,但瀏覽器會在捕獲階段觸發事件對象上的事件。

 事件處理程序

響應某個事件的函數方法,咱們稱之爲事件處理程序(或事件偵聽器)
window.onclick = function() {
    //...
}
// 這裏的function(){}就是事件處理程序

HTML事件處理程序

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級事件處理。

DOM0 級事件處理程序

每一個元素(HTML元素)都有本身的事件處理程序屬性,屬性名一般以on開頭,例如onclick/onmouseover。爲這個屬性的值設置一個函數,就能夠指定事件處理程序。而將其屬性值賦值爲null,則完成解綁。(同個元素沒法綁定多個同名事件)

var myBtn = document.getElementById('myBtn');

// 爲myBtn綁定事件處理程序, 只能綁定一個
myBtn.onclick = function() {
    alert('Hello world!');
}

// 解綁
myBtn.onclick = null;

DOM2 級事件處理程序

"DOM2級事件"定義了兩個方法,addEventListener()/removeEventListener(),用於爲元素綁定和解綁事件。

可綁定多個事件,區別於DOM0級/HTML僅能綁定一個)。

el.addEventListener(eventName, callBack, useCapture)

  • eventName: 事件名稱
  • callBack: 回調函數,當事件觸發時,函數會傳入一個參數event,爲當前的事件對象
  • useCapture: 默認是false,表明事件句柄在冒泡階段執行, true則表明在捕獲階段執行
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級事件處理程序

IE 事件處理程序

IE9如下不支持DOM2級事件,但IE提供了與DOM2級事件相似的兩個方法,attachEvent()/detachEvent,IE9如下不支持事件捕獲,因此attachEvent僅支持冒泡階段觸發,只接收兩個參數(eventName, function)。

// 綁定
myBtn.attachEvent('onclick', handleClick);

// 解綁
myBtn.detachEvent('onclick', handleClick);

TIPS:

  1. 解綁時function必須與傳入attachEvent相同,這點與DOM2級事件相同
  2. 與DOM0級的區別,DOM0級事件處理在元素的做用域運行,而attachEvent事件處理在全局,this指向window
  3. 綁定多個事件處理程序時,執行順序按綁定順序逆反執行(與DOM2級相反)
myBtn.attachEvent('click', function() {
 // step1...
})
myBtn.attachEvent('click', function() {
 // step2...
})
// 執行順序:step2 -> step1

Event 事件對象

常見應用

event.preventDefault()

阻止默認事件

event.stopPropagation()

阻止事件流發生傳遞(冒泡/捕獲)

event.stopImmediatePropagation()

阻止剩餘事件處理函數的執行,並阻止當前事件在事件流上傳遞

event.currentTarget

當前綁定事件的元素

event.target

當前觸發事件的元素

event.stopPropagation()與.stopImmediatePropagation()的區別

同個元素綁定多個同名事件時,stopImmediatePropagation不只阻止了冒泡,並且會阻止後續事件的執行,能夠理解爲增強版的stopPropagation

myBtn.addEventListener('click', function(event) {
    // step1;
    event.stopImmediatePropagation();
})

myBtn.addEventListener('click', function(event) {
    // step2;
    // 我被stopImmediatePropagation阻止掉了!!!
})
currantTarget與target的區別

事件處理程序內部,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有不少個, 但它們有一個共同的父節點ulli的點擊事件會冒泡到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);

事件這塊還剩下一部分知識點,後續文章會再就模擬事件這塊知識點進行拆分詳解。

天冷了,更文不易,望你們多多點贊。

做者:以樂之名本文原創,有不當的地方歡迎指出。轉載請指明出處。
相關文章
相關標籤/搜索