JavaScript/處理 DOM 事件
具有使用者介面的應用程式(以及其他應用程式型別)主要由事件驅動。在這裡,我們重點介紹 DOM 事件。它們起源於(或觸發)瀏覽器或原生應用程式中的特定操作或情況,例如使用者用滑鼠點選、輸入鍵盤鍵或在觸控式螢幕上操作;視覺物件被“拖放”、“複製貼上”或調整大小;HTML 頁面載入或要解除安裝;瀏覽器或視覺物件獲得或失去焦點;等等。應用程式也可以以程式設計方式建立事件(分派)。
事件與其起源物件和 JavaScript 語句相關聯;也就是說,在大多數情況下,它呼叫一個被稱為事件處理程式的函式。JavaScript 部分在事件發生後被呼叫。常見的操作包括與伺服器通訊、驗證使用者輸入或修改 DOM 或圖形。
一個簡短的例子展示了事件如何在 HTML 頁面中定義、觸發和執行。這種語法版本被稱為內聯 JavaScript。
<!DOCTYPE html>
<html>
<head>
<script>
function handleEvent(evt) {
"use strict";
alert("Perform some actions via a JavaScript function.");
}
</script>
</head>
<body id="body" style="margin:2em">
<!-- inline all statements; unusual -->
<button onclick="const msg='Direct invocation of small actions.'; alert(msg);">First button</button>
<!-- call a function (the event handler) -->
<button onclick="handleEvent(event)">Second button</button>
</body>
</html>
當用戶點選其中一個按鈕時,瀏覽器會讀取按鈕的屬性onclick,建立一個包含事件所有屬性的 JavaScript 物件,並執行 JavaScript 語句。大多數情況下,JavaScript 部分是對一個函式的呼叫。該函式被稱為事件處理程式。它接收 JavaScript 物件作為其第一個引數。只有在非常簡單的情況下,完整的 JavaScript 指令碼才被內聯。
當然,被呼叫的函式必須存在於某個地方。一些標準函式,例如alert(),是預定義的,由瀏覽器提供。你自寫的函式存在於 HTML 標籤<script>中,或者該標籤引用了它們存在的檔案或 URL。
提示:將事件定義直接嵌入 HTML 被稱為內聯 JavaScript。這是註冊事件處理程式的最早方法,但對於非平凡的應用程式,它往往會使原始碼難以閱讀。因此,它可以被認為是一種比其他非侵入式技術更不 理想的技術;參見下一章。使用內聯 JavaScript 的方式可以認為與使用內聯 CSS 的方式類似,其中 HTML 透過將 CSS 放入style 屬性來設定樣式。
然而,這本書將在其演示頁面中經常使用內聯 JavaScript,因為其語法簡潔,概念易於理解。
JavaScript 知道兩種為 HTML 元素註冊事件處理程式的方法。首先,事件處理程式函式可以直接分配給元素的屬性onxxx(onclick、onkeydown、onload、onfocus、...)。它們的名稱以 'on' 開頭,以事件型別的值結尾。其次,函式addEventListener註冊事件型別和事件處理程式。
<!DOCTYPE html>
<html>
<head>
<script>
function register() {
"use strict";
const p1 = document.getElementById("p1");
// do it this way ...
p1.onclick = showMessage; // no parenthesis ()
// ... or this way (prefered)
//p1.addEventListener('click', showMessage);
alert("The event handler is assigned to the paragraph.");
}
function showMessage(evt) {
"use strict";
// the parameter 'evt' contains many information about
// the event, e.g., the position from where it originates
alert("A click-event to the paragraph. At position " +
evt.clientX + " / " + evt.clientY + ". Event type is: " + evt.type);
}
</script>
</head>
<body>
<h1>Register an event</h1>
<p id="p1" style="margin:2em; background-color:aqua">
A small paragraph. First without, then with an event handler.
</p>
<button onclick="register()" id="button_1">A button</button>
</body>
</html>
按鈕 'button_1' 的 onclick 事件處理程式 'register' 是使用上面的內聯 JavaScript 語法註冊的。當頁面載入時,只有這個事件處理程式是已知的。點選段落 'p1' 不會觸發任何操作,因為它到目前為止還沒有任何事件處理程式。但是,當按鈕被按下時,按鈕 'button_1' 的處理程式會註冊第二個事件處理程式,即函式 'showMessage'。現在,在點選段落後,就會出現警報“對段落的點選事件...”。
註冊在第 10 行完成p1.onclick = showMessage。與內聯 JavaScript 的明顯區別在於沒有括號。內聯 JavaScript 呼叫函式showMessage,因此需要使用括號。函式register不會呼叫showMessage。它只在註冊過程中使用其名稱。
使用函式addEventListener是將函式分配給段落的 'onclick' 屬性的另一種方法。它作用於元素 'p1' 並接收兩個引數。第一個是事件型別(click、keydown、...)。這些事件型別與onxxx 名稱相對應,只是缺少前兩個字元 'on'。第二個引數是充當事件處理程式的函式的名稱。
你可以透過註釋掉第 10 行或第 12 行來測試示例。使用addEventListener 函式的第 12 行是首選版本。
不同的事件型別取決於起源元素的型別。事件型別的完整列表非常龐大 (MDN) (W3schools)。我們展示了一些重要的型別和示例。
| 名稱 | 描述 |
|---|---|
blur
|
輸入元素失去焦點 |
change
|
元素被修改 |
click
|
元素被點選 |
dblclick
|
元素被雙擊 |
error
|
載入元素時發生錯誤 |
focus
|
輸入元素獲得焦點 |
keydown
|
當元素有焦點時,按下了一個鍵 |
keyup
|
當元素有焦點時,釋放了一個鍵 |
load
|
元素被載入 |
mouseenter
|
滑鼠指標移動到元素內 |
mousemove
|
滑鼠指標在元素內部移動 |
mousedown
|
滑鼠按鈕在元素上按下 |
mouseup
|
滑鼠按鈕在元素上釋放 |
mouseleave
|
滑鼠指標從元素中移出 |
reset
|
表單的重置按鈕被點選 |
resize
|
包含視窗或框架被調整大小 |
select
|
元素中的部分文字被選中 |
submit
|
表單正在提交 |
unload
|
內容正在解除安裝(例如,視窗被關閉) |
以下示例演示了一些不同的事件型別。
<!DOCTYPE html>
<html>
<head>
<script>
function registerAllEvents() {
"use strict";
// register different event types
document.getElementById("p1").addEventListener("click", handleAllEvents);
document.getElementById("p2").addEventListener("dblclick", handleAllEvents);
document.getElementById("p3").addEventListener("mouseover", handleAllEvents);
document.getElementById("t1").addEventListener("keydown", handleAllEvents);
document.getElementById("t2").addEventListener("select", handleAllEvents);
document.getElementById("button_1").addEventListener("mouseover", handleAllEvents);
}
function handleAllEvents(evt) {
"use strict";
alert("An event occurred from element: " +
evt.target.id + ". Event type is: " + evt.type);
}
</script>
</head>
<body onload="registerAllEvents()" style="margin:1em">
<h1 id="h1" style="background-color:aqua">Check Events</h1>
<p id="p1" style="background-color:aqua">A small paragraph. (click)</p>
<p id="p2" style="background-color:aqua">A small paragraph. (double click)</p>
<p id="p3" style="background-color:aqua">A small paragraph. (mouse over)</p>
<p style="background-color:aqua">
<textarea id="t1" rows="1" cols="50">(key down)</textarea>
</p>
<p style="background-color:aqua">
<textarea id="t2" rows="1" cols="50">(select)</textarea>
</p>
<button id="button_1">A button (mouse over)</button>
</body>
</html>
當頁面載入時,body 的onload 事件被觸發。請注意,這裡 'on' 字首是必要的,因為它是在內聯 JavaScript 語法中(第 23 行)。被呼叫的函式registerAllEvents 定位不同的 HTML 元素,並註冊不同型別的事件處理程式(第 8 - 13 行)。通常你會註冊不同的函式,但為了簡單起見,在這個示例中,我們將相同的函式handleAllEvents 註冊到所有元素。這個函式報告事件型別和起源 HTML 元素。
事件始終以 JavaScript 物件的形式作為其第一個引數傳遞給事件處理程式。JavaScript 物件由屬性組成;屬性是鍵值對。在所有情況下,其中一個鍵是 'type'。它包含事件的型別;它的一些可能值在上面的表格中顯示。根據事件型別,還有許多其他屬性可用。
以下是一些或多或少重要的屬性的示例。
| 名稱 | 描述 |
|---|---|
button |
返回被點選的滑鼠按鈕 |
clientX |
返回滑鼠指標在本地座標系內的水平座標:滾出部分不計入 |
clientY |
返回滑鼠指標在本地座標系內的垂直座標:滾出部分不計入 |
code |
返回所按鍵的文字表示,例如,“ShiftLeft” 或 “KeyE” |
key |
返回所按鍵的值,例如,“E” |
offsetX |
返回滑鼠指標在目標 DOM 元素內的水平座標 |
offsetY |
返回滑鼠指標在目標 DOM 元素內的垂直座標 |
pageX |
返回滑鼠指標在頁面座標系內的水平座標 - 包括滾動超出部分 |
pageY |
返回滑鼠指標在頁面座標系內的垂直座標 - 包括滾動超出部分 |
screenX |
返回滑鼠指標在完整顯示器座標系內的水平座標 |
screenY |
返回滑鼠指標在完整顯示器座標系內的垂直座標 |
target |
返回觸發事件的元素 |
timeStamp |
返回元素建立和事件建立之間的時間差(以毫秒為單位) |
type |
返回觸發事件的元素型別 |
x |
clientX 的別名 |
y |
clientY 的別名 |
以下指令碼展示瞭如何訪問屬性。
<!DOCTYPE html>
<html>
<head>
<script>
function changeTitle(evt) {
"use strict";
const xPos = evt.x;
const yPos = evt.y;
document.title = [xPos, yPos];
}
</script>
</head>
<body onmousemove="changeTitle(event)" style="margin:1em; border-width: 1px; border-style: solid;">
<h1 id="h1" style="background-color:aqua">Check Events</h1>
<p id="p1" style="margin:2em; background-color:aqua">A small paragraph.</p>
<p id="p2" style="margin:2em; background-color:aqua">Another small paragraph.</p>
<button id="button_1">Button</button>
</body>
</html>
為 body 註冊一個滑鼠移動事件。每當滑鼠在 body 上移動時,就會觸發該事件。事件處理程式從 JavaScript 物件中讀取 x/y 屬性,並在瀏覽器活動選項卡的標題中顯示它們。
removeEventListener
[edit | edit source]類似於 addEventListener,removeEventListener 函式從元素中移除事件監聽器。
合成事件
[edit | edit source]系統提供了上述豐富的事件型別集。此外,您還可以建立自己的事件,並從您的應用程式中觸發它們。
首先,建立一個帶有一個引數(事件處理程式)的函式。接下來,為元素註冊此事件處理程式。到目前為止,一切都與預定義事件型別相同。
function register() {
"use strict";
// ...
// choose an arbitrary event type (first parameter of 'addEventListener')
// and register function on element
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// the event handler for the non-system event
function f(evt) {
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type);
}
現在您可以在應用程式的任何部分觸發此事件。為此,建立一個與所選型別完全相同的事件,並使用對 dispatchEvent 的呼叫來觸發它。
function triggerEvent(evt) {
"use strict";
// create a new event with the appropriate type
const newEvent = new Event("syntheticEvent");
// trigger this event on element 'p1'
document.getElementById("p1").dispatchEvent(newEvent);
}
出於測試目的,我們將此功能繫結到按鈕。完整頁面現在看起來像這樣
<!DOCTYPE html>
<html>
<head>
<script>
function register() {
"use strict";
document.getElementById("p1").addEventListener("click", showMessage);
document.getElementById("p2").addEventListener("click", showMessage);
document.getElementById("button_1").addEventListener("click", triggerEvent);
// choose an arbitrary event type (first parameter of 'addEventListener')
// and register function on element
document.getElementById("p1").addEventListener("syntheticEvent", f);
}
// the event handler for the non-system event
function f(evt) {
"use strict";
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type);
}
function showMessage(evt) {
"use strict";
// the parameter 'evt' contains many information about
// the event, e.g., the position from where it originates
alert("A click event to element: " + evt.target.id +
" The event type is: " + evt.type);
}
function triggerEvent(evt) {
"use strict";
// create a new event with the appropriate type
const newEvent = new Event("syntheticEvent");
// trigger this event on element 'p1'
document.getElementById("p1").dispatchEvent(newEvent);
}
</script>
</head>
<body onload="register()">
<h1>Create an event programmatically.</h1>
<p id="p1" style="margin:2em; background-color:aqua">A small paragraph.</p>
<p id="p2" style="margin:2em; background-color:aqua">Another small paragraph.</p>
<button id="button_1">Button</button>
</body>
</html>
在一開始,按鈕會監聽點選事件,而 'p1' 也會監聽 'click' 型別的事件以及 'syntheticEvent' 型別的事件。當按鈕被點選時,其事件處理程式 'triggerEvent' 會建立一個新的 'syntheticEvent' 型別事件,並在 'p1' 上觸發它(這是此示例的主要目的)。事件處理程式 showMessage 會顯示一條訊息,而無需點選 'p1'。換句話說:'p1' 上的事件發生,而沒有點選 'p1'。
您可能需要在事件處理程式中獲得來自呼叫函式的一些資料,例如,錯誤訊息的文字、HTTP 響應的資料,...。您可以使用 CustonEvent 及其屬性 'detail' 傳遞此類資料
const newEvent = new CustomEvent("syntheticEvent", {detail: "A short message."});
在事件處理程式中訪問資料
function f(evt) {
"use strict";
alert("Invocation of the synthetic event on: " + evt.target.id +
" The event type is: " + evt.type + ". " + evt.detail);
}
(A)同步行為
[edit | edit source]大多數事件以同步方式處理,例如 'click'、'key' 或 'mouse'。但有一些例外是非同步處理的,例如 'load' [1] [2]。'同步' 意味著呼叫的順序與其建立的順序相同。依次點選按鈕 A、B 和 C 會導致按此順序依次呼叫 A、B 和 C 的事件處理程式。相反,'非同步' 事件會導致相關處理程式的呼叫順序任意。
您必須區分這個問題,即事件處理程式的呼叫,與它們主體的實現。每個實現都可以嚴格按順序執行,也可以包含 非同步呼叫 - 這取決於您的意圖。典型的非同步呼叫是 HTTP 請求或資料庫查詢。當然,它們可以作為點選事件處理程式的一部分。