通過上一節的學習,對DOM事件有了一個簡單的瞭解。但這只是學習DOM事件相關知識的起步點。今天來瞭解一下DOM事件的模型。
基本事件模型
在Web應用程序或Web網站中,可以通過使用者操作或系統的事件,達到相應的響應。而在JavaScript中,事件在未得到標準化之前,各瀏覽器就有一個事件模型 —— 基本事件模型(Basic Event Model)。
在基本事件模型中,要在某個事件發生時,調用指定的函數,也就是上一節中介紹的事件處理程序。這個程序會指定事件觸發將會做什麼樣的事情。打個比方,當Web頁面加載完所有資源之後,即window 的load 事件中做指定的事情:
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事件:當用戶與頁面上的元素交互時觸發,如:
load 、scroll
- 焦點事件:當元素獲得或失去焦點時觸發,如:
blur 、focus
- 鼠標事件:當用戶通過鼠標在頁面執行操作時觸發如:
dbclick 、mouseup
- 滾輪事件:當使用鼠標滾輪或類似設備時觸發,如:
mousewheel
- 文本事件:當在文檔中輸入文本時觸發,如:
input 、change
- 鍵盤事件:當用戶通過鍵盤在頁面上執行操作時觸發,如:
keydown 、keypress
- 合成事件:當為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
|