一、事件對象(Event)概述
1. 事件對象的定義
event 對象是瀏覽器自動生成的對象,當用戶與頁面進行交互時(如點擊、鍵盤輸入、鼠標移動等),事件觸發時就會自動傳遞給事件處理函數。event 對象包含了與事件相關的各種信息,開發者可以通過訪問 event 對象的屬性來獲取具體的事件細節,并執行相應的操作。
2. 事件的類型
在 JavaScript 中,事件類型非常豐富,常見的包括:
- 鼠標事件:click(點擊)、dblclick(雙擊)、mousedown(按下鼠標)、mouseup(釋放鼠標)、mousemove(移動鼠標)、mouseenter(鼠標進入元素)、mouseleave(鼠標離開元素)等。
- 鍵盤事件:keydown(按下鍵盤)、keypress(按下字符鍵)、keyup(釋放鍵盤)。
- 表單事件:submit(提交表單)、focus(聚焦)、blur(失焦)。
- 觸摸事件:touchstart(觸摸開始)、touchmove(觸摸移動)、touchend(觸摸結束)。
- 窗口事件:resize(窗口大小變化)、scroll(滾動)。
通過綁定不同類型的事件,開發者可以實現豐富的交互效果。
注:其實事件一直都是存在的(不管有沒有綁定 或 監聽),它只是沒有事件處理程序而已!!!
JavaScript事件是在瀏覽器文檔(document)窗口中的發生的特定的交互瞬間;而JavaScript和HTML之間的交互行為就是通過事件來觸發的。
事件處理程序:
事件處理程序:我們用戶在頁面中進行的點擊這個動作,鼠標移動的動作,網頁頁面加載完成的動作等,都可以稱之為事件名稱,
即:click、mousemove、load等都是事件的名稱。響應某個事件的函數則稱為事件處理程序,或者叫做事件偵聽器。
二、Event 對象的常見屬性
1.?type
?屬性
type
?屬性返回觸發事件的類型,以字符串形式表示。例如,鼠標點擊觸發的事件類型為?"click"
,鍵盤按下觸發的事件類型為?"keydown"
。這是判斷事件類型的重要依據。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件類型打印</title>
</head><body><button id="clickBtn">點擊按鈕</button><input type="text" id="inputBox" placeholder="輸入內容"><script>const clickBtn = document.getElementById('clickBtn');const inputBox = document.getElementById('inputBox');clickBtn.addEventListener('click', (event) => {console.log(`你觸發了 ${event.type} 事件`);});inputBox.addEventListener('input', (event) => {console.log(`你觸發了 ${event.type} 事件,當前輸入內容為:${inputBox.value}`);});document.addEventListener('keydown', (event) => {console.log(`檢測到 ${event.type} 事件,你按下了 ${event.key} 鍵`);});</script>
</body></html>
2.target:找到事件真正的源頭?
作用:指向用戶實際操作的那個元素(哪怕事件是從父元素冒泡上來的)
案例:點擊列表項顯示被點擊的內容
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>事件類型打印</title>
</head><body><ul id="list"><li>蘋果</li><li>香蕉</li><li>橙子</li></ul><script>list.addEventListener('click', (event) => {// 無論點擊哪個<li>,都會顯示對應的水果名稱console.log(`你點擊了:${event.target.textContent}`);});document.addEventListener('click', (event) => {console.log(event.target); // 輸出被點擊的元素});</script>
</body></html>
3.currentTarget 屬性
currentTarget
?與?target
?不同,它表示當前事件監聽器所綁定的元素。即使事件是從子元素觸發的,currentTarget
?始終指向綁定事件處理函數的元素。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>currentTarget與target對比</title><style>.parent {border: 2px solid blue;padding: 10px;}.child {border: 2px solid green;padding: 10px;}.grand-child {border: 2px solid red;padding: 10px;}</style>
</head><body><div class="parent" id="parent">父元素<div class="child" id="child">子元素<button class="grand-child" id="btn">孫元素按鈕</button></div></div><script>const parent = document.getElementById('parent');parent.addEventListener('click', (event) => {console.log("currentTarget:", event.currentTarget);console.log("target:", event.target);});</script>
</body></html>
4.key:獲取鍵盤按下的字符(推薦使用)?
作用:返回按下的具體按鍵(字母、數字、功能鍵等),替代已廢棄的keyCode
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>key 屬性示例</title>
</head><body><input type="text" id="inputField"><script>const inputField = document.getElementById('inputField');inputField.addEventListener('keydown', (event) => {console.log(`你按下了:${event.key}`);});</script>
</body></html>
5.altKey/ctrlKey/shiftKey:檢測修飾鍵是否按下
作用:判斷是否同時按下了 Alt/Ctrl/Shift 鍵(返回 true/false)
案例:檢測 Ctrl+S 組合鍵,在控制臺打印提示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>修飾鍵檢測</title>
</head><body><script>document.addEventListener('keydown', (event) => {if (event.ctrlKey && event.key === 's') {event.preventDefault(); // 阻止默認保存行為console.log('Ctrl + S 組合鍵被按下');}});</script>
</body></html>
6.button:判斷按下的鼠標按鈕
作用:在鼠標點擊事件中,區分左 / 中 / 右鍵(0 = 左鍵,1 = 中鍵,2 = 右鍵)
案例:點擊鼠標,在控制臺打印按下的按鈕信息
三、事件對象的核心方法
1.阻止默認行為:preventDefault()
用于取消事件的默認動作,如阻止表單提交或鏈接跳轉:
document.querySelector('a').addEventListener('click', (event) => {event.preventDefault(); // 阻止鏈接跳轉console.log('鏈接被點擊,但未跳轉');
});
2 阻止事件冒泡:stopPropagation()
防止事件在DOM層級中繼續向上傳播:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>stopPropagation示例</title><style>#container {width: 200px;height: 200px;background-color: lightblue;padding: 10px;}#child {width: 100px;height: 100px;background-color: lightcoral;}</style>
</head><body><div id="container">父容器<div id="child">子元素</div></div><script>const container = document.getElementById('container');const child = document.getElementById('child');container.addEventListener('click', (event) => {console.log('容器被點擊');});child.addEventListener('click', (event) => {event.stopPropagation(); // 阻止事件冒泡到父容器console.log('子元素被點擊,但不會觸發父元素事件');});</script>
</body></html>
⑴把?event.stopPropagation();注釋掉
3.立即停止事件流:stopImmediatePropagation()
同時阻止冒泡和同一元素上其他監聽器的執行:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>stopImmediatePropagation示例</title><style>button {padding: 10px 20px;font-size: 16px;}</style>
</head><body><button id="button">點擊按鈕</button><script>document.getElementById('button').addEventListener('click', (event) => {console.log('第一個監聽器');event.stopImmediatePropagation();});document.getElementById('button').addEventListener('click', (event) => {console.log('第二個監聽器(不會執行)');});</script>
</body></html>
四、事件流:
瀏覽器層次順序:document -> html -> body -> div父元素 -> input子元素】,document最上層祖先元素, input最下層后代元素。
1.🍀什么是事件流:
事件流是描述從頁面中接收事件的順序【從內到外(冒泡),從外到內(捕獲)】;
IE與原來的NetScape(網景),對于事件流提出的是完全不同的順序。IE團隊提出的是事件冒泡流;NetScape的事件流是事件捕獲流。
? ? 簡單的講:當給一個DIV綁定一個點擊事件,又在DIV里面放一個按扭并給按扭也綁定一個點擊事件,此時你點擊里面按扭的同時,外面DIV的點擊事件也會被觸發。
?
2.🍀事件冒泡:
所謂事件冒泡 就是事件最開始從最具體的元素(文檔中嵌套層次最深的那個點【當前綁定事件的那個元素】)接收,然后逐級向上傳播至最不具體的那個節點(document節點,即最上層的節點)。
阻止事件冒泡方法:event.stopPropagation()
阻止默認行為方法:event.preventDefault()?
注意:這兩個方法屬于event對象中的。
3.🍀事件捕獲:
事件捕獲和事件冒泡剛好【相反】,它是事件從最不具體的節點(document)先接收到事件,然后再向下逐一捕獲至(文檔中嵌套層次最深的那個點【當前綁定事件的那個元素】)。
? ? 簡單地說,事件冒泡和事件捕獲都是一種事件傳遞的機制。這種機制可以使事件在不同級
的元素間傳遞。事件冒泡是從事件觸發的源節點,向父節點傳遞,直到到達最頂節點。而事件
捕獲則是從最頂節點,逐步向下傳遞,直到到達事件觸發的源節點。
? ? 在一些標準的瀏覽器中,如IE9以上,Chrome 、Firefox 、Safari瀏覽器等,支持這兩種冒泡方式。而事實上,
準確的說,是這兩種方式的混合方式。因為W3C采取的就是這兩種方式的結合:先從頂級節點
開始,將事件向下傳遞至源節點,再從源節點冒泡至頂節點。
? ? 而在IE及Opera(以下說的都是老版本的歐朋,新版本的歐朋經檢測是支持事件捕獲的)
下,是不支持事件捕獲的。而這個特點最明顯體現在事件綁定函數上。IE、Opera的事件
綁定函數是attachEvent,而Chrome等瀏覽器則是addEventListener,
? ? 而后者比前者的參數多了一個——這個參數是一個布爾值,這個布爾值由用戶決定,用戶若設為true,則該綁定事件以事件捕獲的形式參與,若為false則以事件冒泡的形勢參與。
而這個參數在IE和Opera瀏覽器中是不存在的——根本原因是它們不支持事件捕獲。
五、📚事件綁定:
1.🍀事件綁定方式1:
HTML事件內嵌綁定,就是將事件直接內嵌內HTML結構標簽元素內的【不推薦用,因為不靈活】?
?
<input type="button" onclick="alert("我是事件綁定方式一:HTML事件處理程序,我是內嵌在HTML結構中的");" value="事件綁定方式一【內嵌】" />
<input type="button" onclick="mupiaoFn()" value="事件綁定方式一【調用】" />
HTML事件處理程序:
function mupiaoFn() {alert("我是事件綁定方式1:HTML事件處理程序");
};
2.🍀事件綁定方式2:
【DOM 0級事件處理程序】就是把一個函數/方法賦給一個事件處理程的 屬性如:id 、class 、元素名等 【用得最多,兼容性好,簡單,靈活,跨瀏覽器 ;缺點:不能綁定多個同類型事件】
?
<input type="button" name="eventBtn2" id="eventBtn2" value="事件綁定方式二【通用屬性綁定】" />
DOM 0級事件處理程序
var Btn2 = document.getElementById("eventBtn2");//給誰綁定事件,就要先獲取誰
綁定事件1:【賦給方式】
Btn2.onclick = function() {alert("我是事件綁定方式二:DOM 0級事件處理程序");
};
綁定事件2:【調用方式】
function publick() {alert("我也是事件綁定方式二:DOM 0級事件處理程序");
};Btn2.onclick = publick; //注:publick后面不要加()括號,否則會變為立即執行函數!
刪除事件:
Btn2.onclick = null;?
注意事項:
- 在DOM0級事件處理程序推出之后,被廣為使用,可是出現了這樣一個問題,當我們希望給同一個元素/標簽綁定多個同類型事件的時候(如,為上面的按扭標簽綁定2個或是個以上的點擊事件),是不被允許的。
- 那么,此時,出現了另一種事件處理程序,就是DOM2級的事件處理程序,【注:沒有DOM1級事件這個概念哦】在DOM2級當中,定義了兩個基本方法,
- 用于處理指定(即綁定)和刪除事件處理程序的操作,分別是addEventListener()和removeEventListener(),IE9+、FireFox、Safari、Chrome和Opera都是支持DOM2級事件處理程序的。
- 對于IE8-,則使用的是IE專有的事件處理程序:兩個類似的方法——attachEvent()與detachEvent()。
3.🍀事件綁定方式3:
【DOM 2級事件處理程序 事件委托】:
除了以上兩種事件綁定方式都需要一個一個的綁定和添加事件,還有一種(事件委托)把事件處理程序掛載在父節點上,利用事件冒泡機制在父節點統一處理事件,這樣就不用一個一個的綁定和添加事件了。
addEventListener()和removeEventListener()監聽事件接收3個參數:
- 事件類型(注:不要加 on)
- 事件函數(事件處理程序)
- 是否捕獲 false:冒泡,true:捕獲
注:在IE瀏覽器中用 attachEvent()和detachEvent()監聽事件接收2個參數:事件類型(注:要加 on), 處理函數/只支持冒泡
例:
<ul id="list"><li class="item">Item 1</li><li class="item">Item 2</li><li class="item">Item 3</li>...
</ul>
DOM 2級事件處理程序 / 監聽事件
var list= document.getElementById("list");//給誰綁定事件,就要先獲取誰
添加監聽事件1:【內嵌方式】
list.addEventListener('click' , function(event) {alert("我是事件綁定方式二:DOM 2級事件處理程序");// const li = event.target;const li = event.target.closest('li'); // closest()方法返回最近的祖先元素if (item.style.backgroundColor) {li.style.backgroundColor = 'red'} else {li.style.backgroundColor = 'blue';}} , false); //false:冒泡,true:捕獲
添加監聽事件2:【調用方式】
list.addEventListener('click' , addevFn , false);function addevFn(event) {alert("我是事件綁定方式二:DOM 2級事件處理程序 【調用方式】");// const li = event.target;const li = event.target.closest('li');if (item.style.backgroundColor) {li.style.backgroundColor = 'red'} else {li.style.backgroundColor = 'blue';}
};
IE8及以下的添加和刪除監聽事件方法:(注:IE9及以上的就用上面的方法啦)
IE8及以下的添加監聽事件1:【內嵌方式】 (注 attachEvent 和 detachEvent方法只傳兩個參數,前面兩個和上面一樣,而第3個參數是因為在IE中默認就是冒泡方式,所以不用傳第3個參數啦)
list.attachEvent("onclick" , function(event) {alert("我是IE8及以下的添加監聽事件方法,【內嵌方式】");
});
IE8及以下的添加監聽事件2:【調用方式】(注:attachEvent 和 detachEvent 事件類型前而一定要加 ?on 才可以哦)
list.attachEvent("onclick" , addevFn3);function addevFn3(event) {alert("我是IE8及以下的添加監聽事件方法,【調用方式】");
};
IE8及以下的刪除監聽事件
Btn3.detachEvent("onclick" , addevFn3);
🍀事件方面性能優化:
? ? 在JavaScript代碼當中,添加到頁面中的事件越多,頁面的性能也就越差。導致這一問題的原因主要有:
- 每個函數都是對象,都會占用內存。內存中對象越多,性能也就越差。
- 必須事先指定所有事件處理程序而導致的DOM訪問次數,會延遲整個頁面的交互就緒時間。
- 為了進行頁面的性能優化,因此我們會采用兩種方法,就是上面提到的——事件委托和事件處理程序的移除。
事件委托的好處:
- 在大量子元素需要綁定事件處理程序的情況下,事件委托可明顯減少內存消耗
- 事件委托實現了動態綁定,后續新增的子節點不需要額外去綁定事件處理程序
事件委托的缺點:
- 事件委托必需要能支持冒泡的事件類型,有些事件是不支持冒泡的 如(1、focus事件;2、blur事件;3、scroll事件;4、mouseenter和mouseleave事;5、mouseover和mouseout事件;6、mousemove事件;7、keypress事件;8、beforeunload事件;9、domcontentloaded事件;10、cut、copy和paste事件等)
- 需要確保事件對象(event.target)是對應要處理的元素的!
📚事件委托:?
關于什么時候使用事件委托,其實,簡單來說,當時一個頁面事件處理程序比較多的時候,我們通常情況下會使用它。
? ? 事件委托主要利用了事件冒泡,只指定一個事件處理程序,就可以管理一個類型的所有事件。例如:我們為整個一個頁面制定一個onclick事件處理程序,
此時我們不必為頁面中每個可點擊的元素單獨設置事件處理程序
? ? 事件委托:給元素的父級或者祖級,甚至頁面綁定事件,然后利用事件冒泡的基本原理,通過事件目標對象進行檢測,然后執行相關操作。其優勢在于:
? ? 大大減少了事件處理程序的數量,在頁面中設置事件處理程序的時間就更少了(DOM引用減少——也就是上面我們通過id去獲取標簽,所需要的查找操作以及DOM引用也就更少了)。
? ? document(注:上面的例子沒有綁定在document上,而是綁定到了父級的div上,最為推薦的是綁定在document上)對象可以很快的訪問到,而且可以在頁面生命周期的任何時點上為它添加事件處理程序,并不需要等待DOMContentLoaded或者load事件。換句話說,只要可單擊的元素在頁面中呈現出來了,那么它就立刻具備了相應的功能。
整個頁面占用的內存空間會更少,從而提升了整體的性能。
?
📚移除事件處理程序?
?
? ? 每當將一個事件處理程序指定給一個元素時,在運行中的瀏覽器代碼與支持頁面交互的JavaScript代碼之間就會建立一個連接。連接數量也直接影響著頁面的執行速度。
所以,當內存中存在著過時的“空事件處理程序”的時候,就會造成Web應用程序的內存和性能問題。
那么什么時候會造成“空事件處理程序”的出現呢?
? ? 文檔中元素存在事件,通過一些DOM節點操作(removeChild、replaceChild等方法),移除了這個元素,但是DOM節點的事件沒有被移除。
innerHTML去替換頁面中的某一部分,頁面中原來的部分存在事件,沒有移除。
頁面卸載引起的事件處理程序在內存中的滯留。
🍀解決方法:
- 合理利用事件委托;
- 在執行相關操作的時候,先移除掉事件,再移除DOM節點;
- 在頁面卸載之前,先通過onunload事件移除掉所有事件處理程序。