跳轉到內容

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 透過將 CSS 放入style 屬性來設定樣式。

然而,這本書將在其演示頁面中經常使用內聯 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]

類似於 addEventListenerremoveEventListener 函式從元素中移除事件監聽器。

合成事件

[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]
華夏公益教科書