跳转到内容

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]
华夏公益教科书