分享

DOM系列:DOM事件模型

 ipilipala 2018-10-07

通過上一節的學習,對DOM事件有了一個簡單的瞭解。但這只是學習DOM事件相關知識的起步點。今天來瞭解一下DOM事件的模型。

基本事件模型

在Web應用程序或Web網站中,可以通過使用者操作或系統的事件,達到相應的響應。而在JavaScript中,事件在未得到標準化之前,各瀏覽器就有一個事件模型 —— 基本事件模型(Basic Event Model)

在基本事件模型中,要在某個事件發生時,調用指定的函數,也就是上一節中介紹的事件處理程序。這個程序會指定事件觸發將會做什麼樣的事情。打個比方,當Web頁面加載完所有資源之後,即windowload事件中做指定的事情:

window.onload = function () {
    // window的load事件發生時要做的事情...
}

除此之外,事件還可以由使用者的操作一些事情來觸發事件。比如在按鈕上綁定一個click事件:

<!-- HTML -->
<button>Click Me!</button>

// Script
let handler = function () {
    console.log(this)
}

document.querySelector('button').onclick = handler

上面的代碼,當用戶用鼠標點擊按鈕時會調用handler()函數,打印出來的this就是用戶點擊的按鈕。像這樣的做法,被稱為傳統模型(Traditional Model)傳統註冊模型(Traditional Registration Model)。這種事件模型也被稱為DOM0級模型

基本事件模型有一個典型的缺點,就是只能註冊一個事處處理程序,如果你想註冊多個事件處理程序是行不通的。比如:

<!-- HTML -->
<button>單擊我</button>

// Script
let handler1 = function () {
    console.log('Handler1:', this)
}

let handler2 = function () {
    console.log('Handler2', this)
}

document.querySelector('button').onclick = handler1
document.querySelector('button').onclick = handler2

當你點擊button按鈕時,瀏覽器控制台只會輸出hander2()函數做的事情:

第一個函數handler1()不起作用。如果你想第一個函數也要起作用,那就需要使用接下來要說的事件模型 —— DOM Level 2模型

在基本事件模型中,如果要移除監聽函數,可以通過給其事件賦值null來實現:

document.querySelector('button').onclick = null

DOM Level 2模型

DOM level 2模型屬於W3C標準模型,現代瀏覽器都支持該模型。在該事件模型中,一次事件共有三個過程:

  • 事件捕獲階段(Capturing Phase):事件從document一直向下傳播到目標元素,依次檢查經歷過的節點是否綁定了事處監聽函數(事件處理程序),如果有則執行,反之不執行
  • 事件處理階段(Target Phase):事件到達目標元素,觸發目標元素的監聽函數
  • 事件冒泡階段(Bubbling Phase):事件從目標元素冒泡到document,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行,反之不執行。

簡而言之:事件一開始從文檔的根節點流向目標對象(捕獲階段),然後在目標對向上被觸發(目標階段),之後再回溯到文檔的根節點(冒泡階段)。

圖片來源於W3C

DOM事件中這三個過程很複雜,但在這篇文章中不做深入闡述,如果感興趣的話,後續的文章將會深入的探討這方面的知識點。

回到DOM Level 2事件模型中,要註冊事件,必須使用addEventListener()方法。比如下面這個示例:

let handler = function () {
    // window的load事件要做的事情...
}
window.addEventListener('load',handler, false)

在基本事件模型中提到過,基本事件模型只能註冊一個事件,但在DOM Level 2事件模型中可以,比如前面提到的按鈕的click事件,我們可以註冊多個事件:

let handler1 = function () {
    console.log('handler1:', this)
}

let handler2 = function () {
    console.log('handler2', this)
}

document.querySelector('button').addEventListener('click', handler1, true)
document.querySelector('button').addEventListener('click', handler2, true)

這個時候點擊按鈕時,handler1()handler2()兩個函數都會被觸發:

在DOM Level 2事件模型中,如果要移除事件處理程序,可以使用removeEventListener()方法。

let btn = document.getElementById('btn');
btn.addEventListener('click', handler, false);
btn.removeEventListener('click', handler, false);

雖然DOM Level 2事件模型是W3C標準事件模型,但低於IE9的瀏覽器是不支持這種事件模型。所以在JavaScript事件模型中,還有第三種事件模型 —— IE事件模型

IE事件模型

在IE事件模型中,需要使用attachEvent()detachEvent()方法來觸發事件和移除事件。在IE事件模型中,其有兩個過程:

  • 事件處理階段(Target Phase):事件到達目標元素,觸發目標元素的監聽函數
  • 事件冒泡階段(Bubbling Phase):事件從目標元素冒泡到document,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行

DOM Level 3事件模型

DOM Level 3事件模型是DOM Level 2的事件模型的升級版,在DOM Level 2事件模型的基礎上添加了更多的事件類型:

  • UI事件:當用戶與頁面上的元素交互時觸發,如:loadscroll
  • 焦點事件:當元素獲得或失去焦點時觸發,如:blurfocus
  • 鼠標事件:當用戶通過鼠標在頁面執行操作時觸發如:dbclickmouseup
  • 滾輪事件:當使用鼠標滾輪或類似設備時觸發,如:mousewheel
  • 文本事件:當在文檔中輸入文本時觸發,如:inputchange
  • 鍵盤事件:當用戶通過鍵盤在頁面上執行操作時觸發,如:keydownkeypress
  • 合成事件:當為IME(輸入法編輯器)輸入字符時觸發,如:compositionstart
  • 變動事件:當底層DOM結構發生變化時觸發,如:DOMsubtreeModified

同時DOM3級事件也允許使用者自定義一些事件。在自定義事件稱之為自定義事件模型

自定義事件模型

事件模型的實現從設計模式的角度來看,是一種觀察者模式或者也叫發佈訂閱模式,訂閱者訂閱一個消息,發佈者發佈這個消息,訂閱者收到消息,這是一種數據流動的方式,使用這個模式的好處是,可以有多個訂閱者,一個發佈者,發佈一條消息,可被多個訂閱者收到。

比如下面這樣的一個事件模型:

;(function(global){
    class Events {
        constructor(){
            this.cache = {};
            this.onceKeys = [];
        }
        on(key, fn){
            if(!this.cache[key]) this.cache[key] = [];
            this.cache[key].push(fn);
        }
        one(key, fn){
            this.cache[key]=[];
            this.on(key, fn);
            this.onceKeys.push(key);
        }
        off(key, fn){
            if(this.cache[key]) this.cache[key] = fn ? this.cache[key].filter(v=>v !== fn) : [];
        }
        emit(key, ...args){
            if(this.cache[key]){
                this.cache[key].forEach(v=>v.apply(null, args))
                if(this.onceKeys.includes(key)){
                    this.cache[key] = [];
                    this.onceKeys = this.onceKeys.filter(v=>v!==key);
                }
            }
        }
    }

    global.Events = new Events();
})(this)

這是一個簡單的自定義事件型型:

  • on()用於綁定事件,參數:事件名稱,事件處理函數
  • emit()用於觸發事件,參數:事件名稱,傳遞給事件處理函數的參數
  • off 用於解除綁定的指定事件, 參數:事件名稱,要解綁的事件函數
  • one 用於綁定一次性事件,只能觸發一次,參數:事件名稱,事件處理函數

如果要使用,可以這樣:

Events.on('cus', (a, b) => console.log(a+b))
Events.emit('cus', 1, 2); // => 3
Events.off('cus');
Events.emit('cus', 1, 2)

// 只觸發一次
Events.one('once', a => console.log(a))
Events.emit('once', 1); // => 1
Events.emit('once', 2);

從事件模型中學到

從DOM事件模型上,我們可以學到很多。可以在項目中使用類似的解耦的概念。應用中的模塊可以有很高的很複雜度,只要它的複雜度被封裝隱藏在一套簡單的接口背後。很多前端框架(比如Backbone.js)都是重度基於事件的,使用發佈/訂閱(Publish & Subscribe)的方式來處理跨模塊間的通信,這點跟DOM非常相似。

基於事件的架構是極好的。它提供給我們一套非常簡單通用的接口,通過針對這套接口的開發,我們能完成適應成千上萬不同設備的應用。通過事件,設備們能準確地告訴我們正在發生的事情以及發生的時間,讓我們隨心所欲地做出響應。我們不再顧慮場景背後具體發生的事情,而是通過一個更高層次的抽象來寫出更加令人驚豔的應用。

總結

這篇文章簡單的介紹了DOM事件的模型。在JavaScript中常見的事件模型有:DOM基本事件模型DOM Level 2事件模型IE事件模型DOM Level 3事件模型自定義事件模型。每種事件模型都有其自己獨具的特性。只有瞭解了DOM事件模型之後,才可以為後續的DOM事件打下一個基礎。

如需轉載,煩請註明出處:https://www./javascript/dom-model.html

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多