---
title: HTML 事件屬性 (HTML Event Attributes)
description: HTML 事件屬性直接在元素上附加 JavaScript 處理程式。 它們的名稱…
url: "https://upskillnet.com/docs/html-%e4%ba%8b%e4%bb%b6%e5%b1%ac%e6%80%a7-html-event-attributes/"
updated: "2026-11-05"
category: HTML
---

# HTML 事件屬性 (HTML Event Attributes)

HTML 事件屬性直接在元素上附加 JavaScript 處理程式。

它們的名稱通常以 `on` 開始，例如 `onclick`、`oninput` 和 `onsubmit`。

```
<button type="button" onclick="console.log('按鈕已啟動');">
  試試看
</button>
```

相應事件發生時，瀏覽器會執行屬性中的 JavaScript 程式碼。

**為了方便維護應用程式，通常應優先使用 `addEventListener()`，而不是行內事件屬性。** 它將行為與標記分開，並支援多個監聽器。

### 39.1 附加處理程式的三種方式

#### HTML 事件屬性

```
<button type="button" onclick="showMessage()">顯示訊息</button>
```

函式必須能從行內處理程式的執行環境存取。在 JavaScript 模組內宣告的函式，不會自動供行內處理程式使用。

#### JavaScript 事件處理屬性

```
const button = document.getElementById("message-button");

button.onclick = showMessage;
```

事件處理屬性一次只保存一個處理程式。再次指派會取代先前的屬性處理程式。

行內 `onclick` 屬性及相應的 JavaScript `onclick` 屬性，不是兩個獨立的處理程式位置。

#### `addEventListener()`

```
button.addEventListener("click", showMessage);
```

它支援多個監聽器，不會取代事件處理屬性中的處理程式。

留意名稱差異：

| 使用情境 | 名稱 |
| --- | --- |
| HTML 屬性 | `onclick` |
| JavaScript 屬性 | `onclick` |
| `addEventListener()` 事件類型 | `"click"` |

應傳入函式，而不是呼叫函式的結果：

```
// 正確。
button.addEventListener("click", showMessage);

// 通常不正確：會立即呼叫函式。
button.addEventListener("click", showMessage());
```

### 39.2 常見事件屬性

以下介紹常見事件屬性，並非完整清單。

| 類別 | 屬性 | 常見用途 |
| --- | --- | --- |
| 啟動 | `onclick`、`ondblclick` | 回應啟動或雙擊 |
| 輸入 | `oninput`、`onchange` | 回應值變更 |
| 表單 | `onsubmit`、`onreset`、`oninvalid` | 處理提交、重設或驗證失敗 |
| 鍵盤 | `onkeydown`、`onkeyup` | 回應按鍵操作 |
| 焦點 | `onfocus`、`onblur` | 回應取得或失去焦點 |
| 指標 | `onpointerdown`、`onpointerup`、`onpointermove` | 回應滑鼠、觸控筆或觸控指標操作 |
| 滑鼠 | `onmousedown`、`onmouseup`、`onmousemove` | 回應滑鼠事件 |
| 載入 | `onload`、`onerror` | 回應載入或資源錯誤 |
| 捲動 | `onscroll`、`onwheel` | 回應捲動或滾輪輸入 |
| 媒體 | `onplay`、`onpause`、`onended` | 回應媒體播放變更 |
| 拖放 | `ondragstart`、`ondragover`、`ondrop` | 支援原生拖放操作 |

許多事件屬性是全域屬性，但是否有用取決於元素。部分屬性具有特定放置位置或行為。

例如，`<body>` 上的某些事件屬性會作為視窗事件的處理程式。

### 39.3 事件物件

處理程式會收到事件物件，其中包含事件資訊：

```
const button = document.getElementById("message-button");

button.addEventListener("click", (event) => {
  console.log(event.type);
  console.log(event.target);
  console.log(event.currentTarget);
});
```

重要屬性包括：

| 屬性 | 意思 |
| --- | --- |
| `type` | 事件名稱 |
| `target` | 事件目標，受 Shadow DOM 等重新指定目標規則影響 |
| `currentTarget` | 目前正在執行監聽器的物件 |
| `defaultPrevented` | 事件的預設行為是否已取消 |

如果按鈕包含巢狀 `<span>`，點擊該 span 時，它可能成為 `target`，而按鈕仍然是 `currentTarget`。

行內處理程式也可以使用 `event`：

```
<button type="button" onclick="console.log(event.type)">
  記錄事件
</button>
```

### 39.4 事件處理程式中的 `this`

在元素的行內處理程式中，`this` 通常指向該元素：

```
<button type="button" onclick="this.textContent = '已啟動'">
  啟動
</button>
```

使用一般函式作為 `addEventListener()` 回呼時，`this` 指向目前執行監聽器的事件目標：

```
button.addEventListener("click", function (event) {
  console.log(this === event.currentTarget); // true
});
```

箭頭函式不會取得新的 `this` 綁定：

```
button.addEventListener("click", (event) => {
  event.currentTarget.textContent = "已啟動";
});
```

使用 `event.currentTarget` 通常能更清晰地表達要操作的元素。

### 39.5 Input 與 Change 事件

`input` 和 `change` 具有不同用途。

```
<label for="display-name">顯示名稱</label>
<input id="display-name" name="displayName" type="text">
```

```
const nameInput = document.getElementById("display-name");

nameInput.addEventListener("input", () => {
  console.log("目前值：", nameInput.value);
});

nameInput.addEventListener("change", () => {
  console.log("已確認的值：", nameInput.value);
});
```

對文字輸入欄位而言：

- `input` 通常在使用者編輯值時觸發。
- `change` 通常在值已改變，而且控制元素失去焦點後觸發。

其他控制元素具有不同的確認行為。例如，核取方塊通常在切換狀態時觸發 `change`。

透過 JavaScript 設定 `.value`，不會自動觸發這些事件。

不要用鍵盤事件取代輸入事件。值也可以透過貼上、語音輸入及其他非一般按鍵方式改變。

### 39.6 表單提交

應監聽表單的 `submit`，而不是只依靠提交按鈕的點擊：

```
<form id="search-form" action="/search" method="get">
  <label for="search-query">搜尋</label>
  <input id="search-query" name="q" type="search" required>

  <button type="submit">搜尋</button>
</form>
```

```
const form = document.getElementById("search-form");

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const data = new FormData(form);
  console.log(data.get("q"));
});
```

表單可以透過鍵盤操作提交，不限於指標啟動。

重要區別包括：

- 內建驗證可以在 `submit` 事件發生前阻止提交。
- `form.requestSubmit()` 遵循提交流程，包括驗證。
- `form.submit()` 會略過約束驗證及 `submit` 事件。

只有在程式碼實作了預期替代行為時，才取消正常提交。

### 39.7 取消預設行為

使用 `preventDefault()` 取消可取消事件的預設行為：

```
form.addEventListener("submit", (event) => {
  event.preventDefault();
  // 在這裏實作替代提交行為。
});
```

它不會停止事件傳播。

在行內處理程式中，返回 `false` 可以取消許多一般事件的預設行為：

```
<form onsubmit="return validateForm()">
  <!-- 表單控制元素 -->
</form>
```

不過，從 `addEventListener()` 回呼返回 `false`，不會取消事件：

```
form.addEventListener("submit", () => {
  return false; // 不會阻止提交。
});
```

應優先明確呼叫 `event.preventDefault()`。

視窗錯誤及離開頁面前的處理程式等具有特殊規則，不要將一般返回值行為套用至所有事件。

### 39.8 事件傳播

許多事件會經過以下階段：

1. **捕獲**：沿祖先元素朝目標傳播。
2. **目標**：到達事件目標。
3. **冒泡**：如果事件支援冒泡，沿祖先元素向外傳播。

傳入選項即可在捕獲階段監聽：

```
document.addEventListener("click", handleClick, {
  capture: true
});
```

兩個方法可以控制傳播：

| 方法 | 效果 |
| --- | --- |
| `stopPropagation()` | 停止繼續傳播至其他目標 |
| `stopImmediatePropagation()` | 也阻止目前目標上尚未執行的監聽器 |

兩者都不會自動取消預設行為。

避免不必要地停止傳播，否則可能干擾其他元件、分析工具或文件層級的互動處理。

並非所有事件都支援冒泡。例如，`focus` 和 `blur` 不會冒泡，而 `focusin` 和 `focusout` 會。

### 39.9 事件委派

事件委派在共用祖先上附加監聽器，處理來自後代元素的事件：

```
<div id="lesson-actions">
  <button type="button" data-lesson="intro">入門</button>
  <button type="button" data-lesson="forms">表單</button>
</div>
```

```
const actions = document.getElementById("lesson-actions");

actions.addEventListener("click", (event) => {
  if (!(event.target instanceof Element)) {
    return;
  }

  const button = event.target.closest("button[data-lesson]");

  if (!button || !actions.contains(button)) {
    return;
  }

  console.log("已選課節：", button.dataset.lesson);
});
```

`closest()` 可以處理按鈕內巢狀內容的點擊。

事件委派適合重複控制元素及動態新增元素。它依賴事件能傳播至祖先，而跨越 Shadow DOM 邊界時需要額外考慮。

### 39.10 監聽器選項與清理

`addEventListener()` 支援實用選項：

```
button.addEventListener("click", showMessage, {
  once: true
});
```

| 選項 | 用途 |
| --- | --- |
| `capture` | 在捕獲階段監聽 |
| `once` | 首次執行後移除監聽器 |
| `passive` | 承諾監聽器不會取消預設行為 |
| `signal` | 允許透過 `AbortSignal` 移除 |

明確清理監聽器：

```
function handleClick() {
  console.log("已啟動");
}

button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);
```

移除時需要相同的事件類型、同一個回呼函式，以及相符的捕獲設定。

AbortController 可以一併管理多個監聽器：

```
const controller = new AbortController();

button.addEventListener("click", handleClick, {
  signal: controller.signal
});

// 移除使用這個 signal 註冊的監聽器。
controller.abort();
```

不要在 passive 監聽器內呼叫 `preventDefault()`，因為它不會取消預設行為。

### 39.11 鍵盤與指標無障礙功能

原生按鈕會透過一般鍵盤啟動及指標操作產生 `click` 事件：

```
<button type="button" id="save-button">儲存</button>
```

```
document.getElementById("save-button")
  .addEventListener("click", saveDraft);
```

通常不需要為原生按鈕另外加入 Enter 及空白鍵處理程式。

相比之下：

```
<!-- 不是完整的無障礙控制元素。 -->
<div onclick="saveDraft()">儲存</div>
```

點擊處理程式不會令 `<div>` 支援鍵盤操作，也不會提供按鈕語意。

也應避免：

- 只在指標停留時提供重要操作。
- 要求雙擊才能執行重要操作。
- 只在 `pointerdown` 執行一般按鈕操作。
- 不必要地覆蓋標準鍵盤快捷鍵。
- 建立只能拖曳、沒有鍵盤替代方式的介面。

實作真正的鍵盤互動時，應使用 `event.key`，而不是已棄用的 `keyCode` 或 `which`。

### 39.12 安全與內容安全政策

行內事件屬性將可執行 JavaScript 與 HTML 混合：

```
<button onclick="showMessage()">顯示訊息</button>
```

限制嚴格的內容安全政策（CSP）通常會阻止行內事件處理程式。

如果政策允許相關外部程式碼，使用 `addEventListener()` 的外部程式碼會較容易管理，但程式碼本身仍然必須符合政策。

不要只為保留事件屬性，而以寬鬆的行內程式碼權限削弱政策。

如果不安全地插入含有事件屬性的不可信 HTML，可能執行程式碼。例如，資源錯誤處理程式是常見的注入方式。

一般使用者文字應使用：

```
message.textContent = userProvidedText;
```

不要使用 `innerHTML` 插入它。

將處理程式移出 HTML 可以改善結構，但本身不能防止跨網站指令碼攻擊（XSS）。仍然需要安全呈現及適當內容清理。

### 39.13 完整範例：輸入、提交與重設

以下獨立網頁預覽學員姓名，並處理示範表單提交。

它使用原生控制元素及 `addEventListener()`，不需要行內事件屬性。

```
id: hong-kong-html-event-attributes
name: HTML 事件 — 無障礙表單示範
type: html
content: |-
  <!doctype html>
  <html lang="zh-Hant-HK">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta
      name="description"
      content="使用原生 HTML 控制元素示範 input、submit 及 reset 事件。"
    >
    <title>HTML 事件 | 學員表單</title>
    <style>
      *,
      *::before,
      *::after {
        box-sizing: border-box;
      }

      body {
        margin: 0;
        color: #222;
        background: #f5f7fa;
        font-family: system-ui, sans-serif;
        line-height: 1.8;
      }

      main {
        max-width: 40rem;
        margin-inline: auto;
        padding: 2rem 1rem;
      }

      h1 {
        color: #005ea8;
        line-height: 1.4;
      }

      form {
        padding: 1.25rem;
        border: 1px solid #767676;
        border-radius: 0.5rem;
        background: white;
      }

      label {
        display: block;
        margin-block-end: 0.35rem;
        font-weight: 700;
      }

      input {
        width: 100%;
        padding: 0.6rem;
        border: 1px solid #767676;
        border-radius: 0.3rem;
        color: #222;
        background: white;
        font: inherit;
      }

      .help {
        color: #444;
      }

      .actions {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem;
        margin-block-start: 1rem;
      }

      button {
        padding: 0.6rem 1rem;
        border: 1px solid #005ea8;
        border-radius: 0.3rem;
        font: inherit;
        cursor: pointer;
      }

      button[type="submit"] {
        color: white;
        background: #005ea8;
      }

      button[type="reset"] {
        color: #005ea8;
        background: white;
      }

      :focus-visible {
        outline: 3px solid #005ea8;
        outline-offset: 3px;
      }

      #submission-status {
        min-height: 1.8em;
      }
    </style>
  </head>
  <body>
    <main>
      <h1>學員表單</h1>

      <p>
        這個示範只會在目前網頁更新內容，
        不會傳送或永久儲存你的姓名。
      </p>

      <noscript>
        <p>
          這個互動示範需要 JavaScript。
          沒有 JavaScript 時，不會啟用提交控制元素。
        </p>
      </noscript>

      <form id="learner-form">
        <label for="learner-name">姓名</label>

        <input
          id="learner-name"
          name="learnerName"
          type="text"
          autocomplete="name"
          maxlength="80"
          aria-describedby="name-help"
          required
          disabled
        >

        <p id="name-help" class="help">
          輸入你想在這個示範使用的姓名。
        </p>

        <p>
          預覽：<span id="name-preview">尚未輸入姓名。</span>
        </p>

        <div class="actions">
          <button id="submit-button" type="submit" disabled>
            確認姓名
          </button>
          <button id="reset-button" type="reset" disabled>
            重設
          </button>
        </div>
      </form>

      <p id="submission-status" role="status"></p>
    </main>

    <script>
      const form = document.getElementById("learner-form");
      const nameInput = document.getElementById("learner-name");
      const preview = document.getElementById("name-preview");
      const status = document.getElementById("submission-status");

      function updatePreview() {
        preview.textContent =
          nameInput.value.trim() || "尚未輸入姓名。";
      }

      nameInput.addEventListener("input", () => {
        nameInput.setCustomValidity("");
        status.textContent = "";
        updatePreview();
      });

      form.addEventListener("submit", (event) => {
        event.preventDefault();

        const name = nameInput.value.trim();

        if (!name) {
          nameInput.setCustomValidity(
            "請輸入不只是空白字元的姓名。"
          );
          nameInput.reportValidity();
          return;
        }

        nameInput.setCustomValidity("");
        status.textContent = `姓名已確認：${name}。`;
      });

      form.addEventListener("reset", () => {
        nameInput.setCustomValidity("");
        status.textContent = "";

        // reset 事件在控制元素完成重設前執行。
        // 在稍後的工作中讀取已還原的值。
        setTimeout(updatePreview, 0);
      });

      nameInput.disabled = false;
      document.getElementById("submit-button").disabled = false;
      document.getElementById("reset-button").disabled = false;

      updatePreview();
    </script>
  </body>
  </html>
```

在這個範例中：

- `input` 配合不同輸入方式更新預覽。
- `submit` 處理按鈕及鍵盤提交。
- 原生驗證檢查必填欄位。
- 自訂驗證拒絕完全由空白字元組成的姓名。
- `reset` 清除自訂驗證，並在控制元素重設後更新預覽。
- 狀態區域讀出確認訊息，不需要移動焦點。
- `textContent` 顯示輸入文字，不會將它當成 HTML 剖析。
- 只有在事件處理程式附加後，才啟用控制元素。

預覽不是即時通知區域，避免每次按鍵都產生通知。較少出現的確認結果使用 `role="status"`。

### 39.14 常見錯誤

| 錯誤 | 較好的做法 |
| --- | --- |
| 將大量程式碼混入 HTML 屬性 | 使用 `addEventListener()` |
| 向 `addEventListener()` 傳入 `"onclick"` | 使用 `"click"` |
| 註冊處理程式時呼叫它 | 傳入函式參照 |
| 以為多次指派事件處理屬性會保留所有處理程式 | 使用多個事件監聽器 |
| 從監聽器返回 `false` 以取消操作 | 呼叫 `preventDefault()` |
| 混淆取消行為與事件傳播 | 使用適當事件方法 |
| 只監聽提交按鈕的點擊 | 監聽表單的 `submit` 事件 |
| 使用鍵盤事件追蹤所有文字變更 | 使用 `input` |
| 以為程式碼改變值會觸發輸入事件 | 明確更新相關狀態 |
| 讓 `<div>` 可點擊，卻沒有完整互動支援 | 使用原生控制元素 |
| 在 passive 監聽器中取消捲動 | 適當選擇監聽器選項 |
| 使用 `innerHTML` 呈現使用者輸入 | 純文字使用 `textContent` |
| 使用不同函式實例移除監聽器 | 保留原本的回呼函式參照 |

**本章總結：** HTML 事件屬性提供行內 JavaScript 處理程式，但應用程式通常更適合使用 `addEventListener()`。應理解事件物件、預設行為、事件傳播及監聽器清理。使用原生控制元素、在表單層級處理提交，並保持內容呈現安全及鍵盤互動可預測。
