跳轉到內容

JavaScript/處理 DOM 事件

來自華夏公益教科書,開放書籍,開放世界



具有使用者介面的應用程式 - 以及其他應用程式型別 - 主要由事件驅動。這裡我們關注 DOM 事件。它們源於(或觸發)瀏覽器或原生應用程式中的特定操作或情況,例如,使用者用滑鼠點選、鍵入鍵盤按鍵或在觸控式螢幕上操作;視覺物件被“拖放”、“複製貼上”或調整大小;HTML 頁面被載入或將要解除安裝;瀏覽器或視覺物件獲得或失去焦點;等等。應用程式也可以以程式設計方式建立事件(分派)。

一個事件與其源物件和 JavaScript 語句相關聯;也就是說,在大多數情況下,呼叫一個稱為事件處理程式的函式。JavaScript 部分在事件發生後呼叫。常見操作包括與伺服器通訊、驗證使用者輸入或修改 DOM 或圖形。

建立和呼叫事件

[編輯 | 編輯原始碼]

嵌入 HTML

[編輯 | 編輯原始碼]

一個簡短的例子展示瞭如何在 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 透過在style屬性中放置 CSS 來進行樣式化。

然而,本書將在其演示頁面中經常使用內聯 JavaScript,因為語法簡短,概念易於理解。

在 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>

當頁面載入時,bodyonload 事件被觸發。請注意,這裡 '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 請求或資料庫查詢。當然,它們可以是點選事件處理程式的一部分。

練習

[edit | edit source]
... 在另一個頁面上提供(點選此處)。

另請參閱

[edit | edit source]
華夏公益教科書