跳转到内容

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 请求或数据库查询。 当然,它们可以作为点击事件的处理程序的一部分。

... 在另一个页面上提供(点击此处)。

另请参阅

[编辑 | 编辑源代码]
华夏公益教科书