跳转到内容

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