DOM 事件是處理 Web 頁面交互的基礎,是掌握前端開發技術的基礎。 W3C協會早在1988年就開始了DOM標準的制定,W3C DOM標準可以分為DOM1,DOM2,DOM3三個版本。 1.Html事件處理 原始事件模型,事件處理程式被設置為html控制項的性質值,一般是html控制項的 onclic ...
DOM 事件是處理 Web 頁面交互的基礎,是掌握前端開發技術的基礎。
W3C協會早在1988年就開始了DOM標準的制定,W3C DOM標準可以分為DOM1,DOM2,DOM3三個版本。
1.Html事件處理
原始事件模型,事件處理程式被設置為html控制項的性質值,一般是html控制項的 onclick、onerror、onload ....
如:
<button type="button" onclcik="console.log('Hello Console!')">ShowConsole</button>
<button type="button" onclick="showFn()">ShowFn</button>
<script>
function showFn() {
alert('Hello World');
}
</script>
優點:
簡單快速,此時代碼的作用域是文檔全局,可以直接使用文檔所有公共變數
缺點:
1、是HTML於JS 強耦合,綁定在一起
2、一個處理程式無法同時綁定多個處理函數
3、執行時機問題:可能會出現 html 上已經顯示控制項,但是關聯的函數還未載入進來,此時用戶點擊,就會執行失敗(你沒辦法告知用戶要等一會兒...)
4、不同瀏覽器對此方式綁定的函數的作用域解釋可能有差異,在部分情況下會導致不同的行為
2.DOM0級事件
就是先取得控制項變數,然後給變數的事件處理程式賦予一個函數,解綁事件處理是將事件處理程式賦為 null
<input id="x1" type="text" value="World!">
<script>
var x1 = document.getElementById('x1');
x1.onclick = function() {
alert('Hello World'+this.value);
}
// x1.onclick = null; 解綁事件
</script>
特點:
- 此方式添加的代碼是註冊在冒泡階段的
- 此方式添加的函數是元素的屬性,作用域是元素範圍,可以引用元素的屬性(使用 this 代表元素本身)
3.DOM2級事件
DOM級別1於1998年10月1日成為W3C推薦標準。1級DOM標準中並沒有定義事件相關的內容,所以沒有所謂的1級DOM事件模型。
在2級DOM中除了定義了一些DOM相關的操作之外還定義了一個事件模型。當事件發生在節點時,目標元素的事件處理函數就被觸發,而且目標的每個祖先節點也有機會處理那個事件。
DOM事件流包括兩個階段:
1.capturing 消息捕獲階段,事件從Document對象沿著文檔樹向下傳播給節點。如果目標的任何一個祖先專門註冊了事件監聽函數,那麼在事件傳播的過程中就會運行這些函數。
2.下一個階段發生在目標節點自身,直接註冊在目標上的適合的事件監聽函數將運行。
3.bubbling 消息冒泡階段,這個階段事件將從目標元素向上傳播回Document對象(與capturing相反的階段)。雖然所有事件都受capturing階段的支配,但並不是所有類型的事件都bubbling。(0級DOM事件模型處理沒有capturing階段)
老版本的瀏覽器不支持消息捕獲階段的處理。一般都只需要處理消息冒泡階段的事件。
如果想在事件發生前捕獲,然後阻止預設動作發生,可以指定事件處理程式在捕獲階段運行。
所謂事件冒泡就是事件像泡泡一樣從最開始生成的地方一層一層往上冒,一層一層向上直至最外層的html或document。
<div id="box">
<a id="child">事件冒泡</a>
</div>
<script>
var box = document.getElementById('box'),
child = document.getElementById('child');
child.addEventListener('click', function() {
alert('我是目標事件');
}, false);
box.addEventListener('click', function() {
alert('事件冒泡至DIV');
}, false);
</script>
上面的代碼運行後我們點擊a標簽,首先會彈出’我是目標事件’提示,然後又會彈出’事件冒泡至DIV’的提示,這便說明瞭事件自內而外向上冒泡了。
DOM2級標準中,事件定義了addEventListener和removeEventListener兩個方法,分別用來綁定和解綁事件。
方法中包含3個參數,分別是:
(1)綁定的事件處理屬性名稱(不包含on)
(2)處理函數
(3)是否在捕獲時執行,省略此參數時,預設為false,表示僅在冒泡階段處理
DOM2級事件允許給一個處理程式添加多個處理函數。代碼如下:
<button id="btn" type="button"></button>
<script>
var btn = document.getElementById('btn');
function showFn() {
alert('Hello World');
}
btn.addEventListener('click', showFn, false);
btn.addEventListener('mouseover', showFn, false); // 添加一個滑鼠移入的方法
// btn.removeEventListener('click', showFn, false); 解綁事件
</script>
如果是 IE8 以下需要用attachEvent和detachEvent來實現
btn.attachEvent('onclick', showFn); // 綁定事件
btn.detachEvent('onclick', showFn); // 解綁事件
\\ 這裡我們不需要傳入第三個參數,因為IE8級以下版本只支持冒泡型事件。
4.事件對象
DOM當中發生事件時,所有信息都會被收集並存儲到一個event對象當中
可以在調試台查看 event 對象的結構
let btn = document.getElementById('mybtn');
btn.onclick = function(event){
console.log(event);
}
btn.addEventListener("click",(event)=>{
console.log(event);
// event.stopPropagation();
// 加上上面這一句,則按鈕以上層次的就監聽不到 click 事件了,事件冒泡被終止
});
document.body.onclick = (event)=>{
console.log(event);
}
event對象有一些重要屬性,以下是必須牢記的:
- type 代表事件類型,例如:click、mouseover...
- target 代表事件的目標控制項
- currentTarget 代表事件監聽者控制項,等於 this
- eventPhase 事件處理程式的階段:1.捕獲 2.到達目標 3.冒泡階段
- shiftKey bool類型,發生事件時 shift 鍵是否按下
- ctrlKey bool類型,發生事件時 ctrl 鍵是否按下
- altKey bool類型,發生事件時 Alt 鍵是否按下
還有一些方法: - stopPropagation 方法:取消事件冒泡
- preventDefault 方法:阻止預設事件的發生
在上面的代碼當中,點擊按鈕 btn 監聽click 事件,輸出的結果中 event 的 target、currentTarget 都等於 this,就是按鈕對象自身;
但在頁面的 body 的監聽當中,接收到的 event 當中,currentTarget 和 this 代表 body,target 當表點擊發生的真正對象 btn
// 採用 preventDefault 方法,阻止超鏈接的預設動作
let link = document.getElementById("mylink");
link.onclick = (event){
// 此處可以加上 用戶狀態判斷...
event.preventDefault();
}
採用 event 的 type 屬性,可以簡化事件處理函數
let btn = document.getElementById("mybtn");
let handler = (event)=>{
switch(event.type){
case "click":
console.log("clicked");
break;
case "mouseover":
console.log("mouseover");
break;
default:
console.log(event.type);
break;
}
}
btn.onclick = handler;
btn.onmouseover = handler;
5.DOM3級事件
DOM3級事件在DOM2級事件的基礎上添加了更多的事件類型,全部類型如下:
UI事件,當用戶與頁面上的元素交互時觸發,如:load、unload、scroll
焦點事件,當元素獲得或失去焦點時觸發,如:blur、focus
滑鼠事件,當用戶通過滑鼠在頁面執行操作時觸發如:dbclick、mouseup
滾輪事件,當使用滑鼠滾輪或類似設備時觸發,如:mousewheel
文本事件,當在文檔中輸入文本時觸發,如:textInput
鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發,如:keydown、keypress
合成事件,當為IME(輸入法編輯器)輸入字元時觸發,如:compositionstart,compositionupdate,compositionend
變動事件,當底層DOM結構發生變化時觸發,如:DOMsubtreeModified
同時DOM3級事件也允許使用者自定義一些事件。
一些常用的事件:
// load:頁面載入事件
window.addEventListener("load",(event)=>{
console.log("html文檔已經載入完畢");
});
// DOMContentLoaded: 網頁DOM樹構建完成後觸發,並不等待圖像等附加資源完成
// beforeunload:可以發生在網頁內容卸載之前,可以阻止(例如要求用戶進行保存)
window.addEventListener("beforeunload",(event)=>{
console.log("文檔保存...");
});
// unload:可以發生在網頁導航,載入新網頁時, 舊內容已經卸載
window.addEventListener("unload",(event)=>{
console.log("文檔已經卸載...");
});
// error:添加全局錯誤的監聽,如果頁面內圖像未載入成功,就將圖像替換為另一張圖像
window.addEventListener("error",(event)=>{
console.log('body knows:',event);
console.log(event.target.tagName);
if (event.target.tagName=="IMG"){
console.log(event.target);
event.target.src = 'img/404.jpg';
}
},true);
// resize:視窗縮放事件
window.addEventListener("resize",(event)=>{
console.log("height=",window.innerHeight);
console.log("width=",window.innerWidth);
});
// textInput:監聽輸入內容
// textInput 不同於 keyPress,只監聽輸入區內容,且可以通過 event.data 獲得輸入的內容
let myTxt = document.getElementById("myInput");
myTxt.addEventListener("textInput",(event)=>{
console.log(event.data);
if (event.data=="yyds"){
event.target.value = event.target.value.replace(event.data,"永遠的神");
}
});
6.時鐘事件
setTimeout函數,可以利用系統時鐘,創建一個被時鐘喚醒的函數,來執行功能;
setInterval函數,可以利用系統時鐘,創建一個以固定間隔執行任務的程式。
// 創建一個5秒鐘以後執行的程式
let timer1 = setTimeout(()=>{ console.log('一次性任務');},5000);
// 如果在timer1執行前調用此函數,可以取消時鐘任務
function killTimer(){
clearTimeout(timer1);
}
// 創建每秒報時的報時器
let timer2 = setInterval(()=>{
console.log(new Date());
},1000);
// 清除報時器
function killInteTimer(){
clearTimeout(timer2);
}
7.SPA應用中的事件處理框架
為了避免在html控制項上直接添加事件處理程式與監聽程式,避免複雜凌亂的事件函數,避免html與JS代碼的混合,以及避免執行時機、記憶體泄露等問題,
可以利用 DOM 事件機制,利用事件捕獲以及事件冒泡,在 DOM 的頂層節點上添加事件監聽處理函數,將用戶交互事件處理入口歸併到一處。
主要原理是,在需要交互的控制項上添加自定義屬性,並賦值,在DOM頂層監聽中,利用截獲的 event.target 獲得事件發生的控制項,
利用控制項的 attributes 數組獲得自定義屬性的值,以這些值作為參數,用於事件處理函數。
// 以下程式中,控制項上添加了 ele_type 自定義屬性
// Html控制項
<span id="lab_username" ele_type="username" ele_data="Bruce">精思入神</button>
<p ele_type="userinfo">這個用戶剛剛註冊,還沒有填寫個人信息</p>
// 統一的處理入口
document.addEventListener('click',function(event){
let t = event.target;
if (!t.attributes.ele_type){
return;
}
let type = t.attributes.ele_type.value;
if (type=="username"){
console.log("username clicked!")
let userid = t.attributes.ele_data.value;
console.log(userid); // 輸出 Bruce
}
if (type=="userinfo"){
console.log("userinfo clicked!")
}
})
8.事件模擬
可以利用代碼模擬事件。可以用在程式邏輯需要的地方,不需要用戶動作,自動提供事件
let btn = document.getElementById("mybtn");
// 創建事件
let m_event = document.createEvent("MouseEvents");
// 初始化為滑鼠click事件
m_event.initMouseEvent("click",true,true,document.defaultView,0,0,0,0,0,false,false,false,false,0,null);
// 通過 btn 按鈕發送出去(此 event 的target自動賦值為當前按鈕)
btn.dispatchEvent(m_event);
9.用戶自定義事件
利用用戶自定義事件,可以在控制項、模塊之間傳遞消息,不用在模塊之間直接函數調用,可以讓相互調用更加靈活。
// 自定義事件
let div = document.getElementById('myInput');
// 定義用戶自定義事件監聽程式,app_event 是自己定義的全新消息類型
div.addEventListener("app_event",(event)=>{
console.log(event.detail);
});
// 激發用戶自定義事件
function sendAppEvent(){
if (document.implementation.hasFeature("CustomEvents","3.0")){
event = document.createEvent("CustomEvent");
event.initCustomEvent('app_event',true,false,'Hello App!');
div.dispatchEvent(event);
}
}
參考文獻:
- DOM事件機制: https://blog.csdn.net/caseywei/article/details/81435645
- JS中DOM0,DOM2,DOM3級事件模型解析: https://www.jianshu.com/p/bbd6e600c0d3
- 20個優秀的 JavaScript 鍵盤事件處理庫: https://www.cnblogs.com/lhb25/p/20-javascript-libraries-to-handle.html