簡介

在瀏覽器中的 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 時,您可以檢查按下了哪個按鍵,以可讀的格式(例如 EscapeEnter 等),可以通過檢查 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()
})

常用事件

以下是您可能會處理的最常見事件列表。

載入

當頁面載入完成時,在 windowbody 元素上觸發 load 事件。

鼠標事件

單擊鼠標按鈕時觸發 click。雙擊時觸發 dblclick。當然,在此事件之前會觸發 click

mousedownmousemovemouseup 可以組合使用以跟踪拖放事件。請小心使用 mousemove,因為它在滑鼠移動期間觸發多次(見下文中的 節流)。

鍵盤事件

當按下鍵盤按鈕時觸發 keydown(當按鈕保持按下時,該鍵重複觸發)。鬆開按鍵時觸發 keyup

滾動

當滾動頁面時,scroll 事件在 window 上觸發。在事件處理程序中,可以通過檢查 window.scrollY 來獲取當前滾動位置。

請牢記,此事件不是一次性的。在滾動期間,它會多次觸發,不僅在滾動的結尾或開頭 - 請勿在處理程序中進行任何繁重的計算或操作,而應該使用 節流

節流

正如上面所提到的,mousemovescroll 是兩個不僅一次性處理的事件,而是在整個操作的持續時間中連續調用其事件處理程序。

這是因為它們提供了坐標,因此您可以跟踪正在發生的事情。

如果在事件處理程序中執行複雜的操作,您將影響性能並導致用戶在網站上經歷到卡頓。

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
})