簡介
在瀏覽器中的 JavaScript 使用事件驅動的程式設計模型。
一切都是從遵循事件開始。
事件可能是 DOM 載入完成,或一個非同步請求完成取得資源,或是使用者點擊元素或滾動頁面,或是使用者在鍵盤上輸入等等。
有很多不同種類的事件。
事件處理程序
您可以使用事件處理程序來回應任何事件,它是在事件發生時呼叫的函式。
您可以為同一個事件註冊多個處理程序,當該事件發生時,它們都將被呼叫。
JavaScript 提供三種方式來註冊事件處理程序:
內聯事件處理程序
這種風格的事件處理程序很少被使用,因為它的限制,但在 JavaScript 的早期,這是唯一的方式:
<a href="site.com" onclick="dosomething();">A link</a>
DOM on-event 處理程序
這通常用於只有一個事件處理程序的對象上,因為在這種情況下無法添加多個處理程序:
window.onload = () => {
//window loaded
}
在處理 XHR 請求時最常遇到:
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
//.. do something
}
您可以使用 if ('onsomething' in window) {}
檢查是否已經為屬性指定了處理程序。
使用 addEventListener()
這是現代的方式。這種方法允許我們註冊所需數量的處理程序,並且這是最常見的方式:
window.addEventListener('load', () => {
//window loaded
})
注意,IE8 及更低版本不支援這種方式,而是使用自己的
attachEvent()
API。如果需要支援較早的瀏覽器,請牢記這一點。
監聽不同的元素
您可以在 window
上監聽“全局”事件,例如鍵盤的使用,並且可以在特定元素上監聽事件,以檢查它們上發生的事件,例如在按鈕上點擊滑鼠。
這就是為什麼 addEventListener
有時被稱為在 window
上使用,有時被稱為在 DOM 元素上使用。
事件對象
事件處理程序的第一個參數是一個 Event
對象:
const link = document.getElementById('my-link')
link.addEventListener('click', event => {
// link clicked
})
此對象包含許多有用的屬性和方法,例如:
target
,起始該事件的 DOM 元素type
,事件的類型stopPropagation()
,在 DOM 中停止事件的傳播
(查看完整列表)。
其他屬性由特定類型的事件提供,因為 Event
是不同特定事件的界面:
每個事件都有一個相關聯的 MDN 頁面,您可以查看其所有屬性。
例如,當發生 KeyboardEvent 時,您可以檢查按下了哪個按鍵,以可讀的格式(例如 Escape
、Enter
等),可以通過檢查 key
屬性來完成:
window.addEventListener('keydown', event => {
// key pressed
console.log(event.key)
})
在鼠標事件中,我們可以檢查按下了哪個鼠標按鈕:
const link = document.getElementById('my-link')
link.addEventListener('mousedown', event => {
// mouse button pressed
console.log(event.button) //0=left, 2=right
})
事件冒泡和事件捕獲
冒泡和捕獲是事件用於傳播的兩種模型。
假設您的 DOM 結構是這樣的:
<div id="container">
<button>Click me</button>
</div>
您要追踪當用戶點擊按鈕時,並且有 2 個事件監聽器,一個在 button
上,一個在 #container
上。請記住,子元素的點擊事件始終將傳播到其父元素,除非您停止傳播(稍後見)。
這些事件監聽器將按照決定事件冒泡/捕獲模型的順序被呼叫。
冒泡意味著事件從被點擊的項目(子項)向上傳播到其所有父節點,開始於最近的一個。
在我們的例子中,button
上的處理程序將在 #container
上的處理程序之前被觸發。
捕獲是相反的:外部事件處理程序在更具體的處理程序——button
上的處理程序之前被觸發。
預設情況下,所有事件都會冒泡。
您可以選擇通過將 addEventListener 的第三個參數設置為 true
以採用事件捕獲:
document.getElementById('container').addEventListener(
'click',
() => {
//window loaded
},
true
)
請注意,首先執行所有捕獲事件處理程序。
然後執行所有冒泡事件處理程序。
其順序遵循此原則:DOM 從窗口對象開始經歷所有元素,並查找被點擊的項目。在此過程中,它調用與事件關聯的任何事件處理程序(捕獲階段)。
一旦它到達目標,然後再次沿著父節點的樹狀結構重複此過程,直到達到窗口對象,再次調用事件處理程序(冒泡階段)。
阻止傳播
DOM 元素上的事件將傳播到其所有父元素樹,除非停止傳播。
<html>
<body>
<section>
<a id="my-link" ...>
a
上的點擊事件將傳播到 section
,然後到 body
。
您可以在事件處理程序的末尾調用事件的 stopPropagation()
方法來停止傳播:
const link = document.getElementById('my-link')
link.addEventListener('mousedown', event => {
// process the event
// ...
event.stopPropagation()
})
常用事件
以下是您可能會處理的最常見事件列表。
載入
當頁面載入完成時,在 window
和 body
元素上觸發 load
事件。
鼠標事件
單擊鼠標按鈕時觸發 click
。雙擊時觸發 dblclick
。當然,在此事件之前會觸發 click
。
mousedown
,mousemove
和 mouseup
可以組合使用以跟踪拖放事件。請小心使用 mousemove
,因為它在滑鼠移動期間觸發多次(見下文中的 節流)。
鍵盤事件
當按下鍵盤按鈕時觸發 keydown
(當按鈕保持按下時,該鍵重複觸發)。鬆開按鍵時觸發 keyup
。
滾動
當滾動頁面時,scroll
事件在 window
上觸發。在事件處理程序中,可以通過檢查 window.scrollY
來獲取當前滾動位置。
請牢記,此事件不是一次性的。在滾動期間,它會多次觸發,不僅在滾動的結尾或開頭 - 請勿在處理程序中進行任何繁重的計算或操作,而應該使用 節流。
節流
正如上面所提到的,mousemove
和 scroll
是兩個不僅一次性處理的事件,而是在整個操作的持續時間中連續調用其事件處理程序。
這是因為它們提供了坐標,因此您可以跟踪正在發生的事情。
如果在事件處理程序中執行複雜的操作,您將影響性能並導致用戶在網站上經歷到卡頓。
像 Lodash 這樣提供節流功能的函式庫,需要使用 100 多行代碼來處理各種可能的用例。以下是一個簡單且易於理解的實現,它使用 setTimeout 來每 100 毫秒快取滾動事件:
let cached = null
window.addEventListener('scroll', event => {
if (!cached) {
setTimeout(() => {
//you can access the original event at `cached`
cached = null
}, 100)
}
cached = event
})