跳轉到內容

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”。現在,在點選段落後,會彈出警報“A click-event to the paragraph...” 。

註冊是在第 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 註冊了一個 mouse-move 事件。 每當滑鼠在 body 上移動時,就會觸發該事件。 事件處理程式從 JavaScript 物件中讀取 x/y 屬性並在瀏覽器活動標籤的標題中顯示它們。

removeEventListener

[編輯 | 編輯原始碼]

addEventListener 相似,函式 removeEventListener 從元素中刪除事件監聽器。

合成事件

[編輯 | 編輯原始碼]

系統提供了以上所示的豐富事件型別集。 此外,您可以建立自己的事件並從應用程式中觸發它們。

首先,您建立一個帶有一個引數(事件處理程式)的函式。 接下來,您為元素註冊此事件處理程式。 到目前為止,一切都與預定義事件型別相同。

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)同步行為

[編輯 | 編輯原始碼]

大多數事件是同步處理的,例如 'click'、'key' 或 'mouse'。 但是,有一些例外是非同步處理的,例如 'load' [1] [2]。 '同步' 意味著呼叫順序與它們的建立順序相同。 點選按鈕 A、B,然後 C 會導致以完全相同的順序呼叫 A、B,然後 C 的事件處理程式。 相反,'非同步' 事件會導致相關處理程式以任意順序呼叫。

您必須區分此問題(事件處理程式的呼叫)與其實現的函式體。 每個實現可以嚴格按順序執行,也可以包含 非同步呼叫 - 這取決於您的意圖。 典型的非同步呼叫是 HTTP 請求或資料庫查詢。 當然,它們可以作為點選事件的處理程式的一部分。

... 在另一個頁面上提供(點選此處)。

另請參閱

[編輯 | 編輯原始碼]
華夏公益教科書