Skip to content

事件

markdown
---
title: 事件
updated: 2022-11-15T15:18:03
created: 2022-10-24T21:54:56
---

MDN中事件的详细介绍:事件

事件是您在编程时系统内发生的动作或者发生的事情,系统响应事件后,如果需要,您可以某种方式对事件做出回应。例如:如果用户在网页上单击一个按钮,您可能想通过显示一个信息框来响应这个动作。

事件是您在编程时系统内发生的动作或者发生的事情——系统会在事件出现时产生或触发某种信号,并且会提供一个自动加载某种动作(例如:运行一些代码)的机制。

每个可用的事件都会有一个事件处理器,也就是事件触发时会运行的代码块。当我们定义了一个用来回应事件被激发的代码块的时候,我们说我们注册了一个事件处理器。注意事件处理器有时候被叫做事件监听器——从我们的用意来看这两个名字是相同的,尽管严格地来说这块代码既监听也处理事件。监听器留意事件是否发生,然后处理器就是对事件发生做出的回应。

网络事件不是 JavaScript 语言的核心——它们被定义成内置于浏览器的 JavaScript APIs。

javascript
const btn = document.querySelector('button');

function random(number) {
  return Math.floor(Math.random() * (number + 1));
}

// btn.onclick类似于btn.textContent
btn.onclick = function() {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

1. 事件处理程序属性(DOM 0级事件处理程序)

有许多的事件处理参数,将 btn.onclick 依次换成其他值,在浏览器中观察效果:

  • btn.onfocusbtn.onblur — 颜色将于按钮被置于焦点或解除焦点时改变(尝试使用 Tab 移动至按钮上,然后再移开)。这些通常用于显示有关如何在置于焦点时填写表单字段的信息,或者如果表单字段刚刚填入不正确的值,则显示错误消息。(Tab键触发效果)
  • btn.ondblclick — 颜色将仅于按钮被双击时改变。(双击触发效果)
  • window.onkeypress, window.onkeydown, window.onkeyup — 当按钮被按下时颜色会发生改变。keypress 指的是通俗意义上的按下按钮 (按下并松开),而 keydown 和 keyup 指的是按键动作的一部分,分别指按下和松开。注意如果你将事件处理器添加到按钮本身,它将不会工作 — 我们只能将它添加到代表整个浏览器窗口的 window对象中。
  • btn.onmouseover (鼠标碰到触发)和 btn.onmouseout (鼠标碰到按钮时移开触发) — 颜色将会在鼠标移入按钮上方时发生改变,或者当它从按钮移出时。

如果你需要对多个按钮添加事件处理器,可使用:

javascript
const buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}

2. DOM Level 2 Events(DOM 2级事件处理程序)

有一种新的事件触发机制,与onclick类似的,它提供了一个函数,addEventListener()

javascript
btn.addEventListener('click', bgChange);

btn.onclick=bgChange; 效果类似。

addEventListener()有一个相对应的方法removeEventListener(),用于移除事件监听器,在大型,复杂的项目中,是非常有用的,以及其他一些场景,比如需要在不同环境下运行不同的事件处理器,您只需要恰当地删除或者添加事件处理器即可。

3. 其他事件处理机制

还有两种处理机制,其中一种为内联事件处理器,在HTML中添加事件处理器,已被淘汰。另一种为IE事件处理程序。

1和2这两种机制是相对可互换的,至少对于简单的用途:

  • 事件处理程序属性功能和选项会更少,但是具有更好的跨浏览器兼容性 (在 Internet Explorer 8 的支持下),您应该从这些开始学起。
  • DOM Level 2 Events (addEventListener(), etc.) 更强大,但也可以变得更加复杂,并且支持不足(只支持到 Internet Explorer 9)。但是您也应该尝试这个方法,并尽可能地使用它们。主要优点是,如果需要的话,可以使用removeEventListener()删除事件处理程序代码,而且如果有需要,可以向同一类型的元素添加多个监听器。

事件概念

1. 事件对象

有时候在事件处理函数内部,可能会看到一个固定指定名称的参数,例如eventevt或简单的e。这被称为事件对象,它被自动传递给事件处理函数,以提供额外的功能和信息。

javascript
function bgChange(e) {
  const rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  
  // e.target指的是按钮本身,事件对象的 e 的 target 属性始终是事件刚刚发生的元素的引用
  e.target.style.backgroundColor = rndCol;
  console.log(e);
}

btn.addEventListener('click', bgChange);

2. 阻止默认行为

有些事件会具有一些默认的行为,例如自定义注册web表单,当你点击提交按钮时,默认行为是将数据提交到服务器上的指定页面进行处理,并将浏览器重定向到某种类似于'成功注册'的页面。

当用户没有正确提交数据时,麻烦就来了 - 作为开发人员,你希望停止提交信息给服务器,并给他们一个错误提示,告诉他们什么做错了,以及需要做些什么来修正错误。一些浏览器支持自动的表单数据验证功能,但由于许多浏览器不支持,因此建议你不要依赖这些功能,并实现自己的验证检查,这就需要用到事件了。

3. 事件冒泡及捕获

在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。

捕获阶段

  • 浏览器检查元素的最外层祖先<html>,是否在捕获阶段中注册了一个onclick事件处理程序,如果是,则运行它。
  • 然后,它移动到<html>中单击元素的下一个祖先元素,并执行相同的操作,然后是单击元素再下一个祖先元素,依此类推,直到到达实际点击的元素。

document --> <html> --> <body> --> <div>

冒泡阶段

  • 浏览器检查实际点击的元素是否在冒泡阶段中注册了一个onclick事件处理程序,如果是,则运行它
  • 然后它移动到下一个直接的祖先元素,并做同样的事情,然后是下一个,等等,直到它到达<html>元素。

<div> --> <body> --> <html> --> document

DOM事件流

按照W3C的标准,先发生捕获事件,后发生冒泡事件。如果一个元素已经执行了有两个同样的事件,但一个是捕获,一个是冒泡,只执行捕获事件。

javascript
father.addEventListener('click', function() { console.log('father捕获') }, true);
father.addEventListener('click', function() { console.log('father冒泡') }, false);
son.addEventListener('click', function() { console.log('son捕获') }, true);
son.addEventListener('click', function() { console.log('son冒泡') }, false);

// 输出顺序:father捕获 -> son捕获 -> son冒泡 -> father冒泡

修复冒泡问题

在现代浏览器中,默认情况下,所有事件处理程序都在冒泡阶段进行注册。

html
<button>播放视频</button>
<div class="hidden">
  <video>
    <source src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.mp4" type="video/mp4">
    <source src="https://raw.githubusercontent.com/mdn/learning-area/master/javascript/building-blocks/events/rabbit320.webm" type="video/webm">
    <p>Your browser doesn't support HTML video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
  </video>
</div>
<script>
  const btn = document.querySelector('button');
  const videoBox = document.querySelector('div');
  
  function displayVideo() {
    if (videoBox.getAttribute('class') === 'hidden') {
      videoBox.setAttribute('class', 'showing');
    }
  }

  // 若div class=hidden,点击class=showing
  btn.addEventListener('click', displayVideo);
  
  // <div class='hidden'> 停止播放,video被隐藏
  videoBox.addEventListener('click', () => videoBox.setAttribute('class', 'hidden'));

  const video = document.querySelector('video');
  video.addEventListener('click', (e) => video.play());
</script>

沿着这个事件冒泡线路:

  • 它发现了(video) video.onclick...事件处理器并且运行它,因此这个视频第一次开始播放。
  • 接着它发现了(往外冒泡找到的)(div) videoBox.onclick...事件处理器并且运行它,因此这个视频<video>也隐藏起来了。

现在我们要修复它,标准事件对象具有可用的名为 stopPropagation()的函数,当在事件对象上调用该函数时,它只会让当前事件处理程序运行,但事件不会在冒泡链上进一步扩大,因此将不会有更多事件处理器被运行 (不会向上冒泡)。将上述代码框中第26行改为以下代码:

JavaScript
video.addEventListener('click', (e) => {
  e.stopPropagation();
  video.play();
});

4. 事件委托

事件冒泡还允许我们利用事件委托——这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并让子节点上发生的事件冒泡到父节点上,而不是每个子节点单独设置事件监听器。

一个很好的例子是一系列列表项,如果你想让每个列表项被点击时弹出一条信息,您可以将click单击事件监听器设置在父元素<ul>上,这样事件就会从列表项冒泡到其父元素<ul>上。

HTML
<body>
  <ul id="parent-list">
    <li id="post-1">Item 1</li>
    <li id="post-2">Item 2</li>
    <li id="post-3">Item 3</li>
    <li id="post-4">Item 4</li>
    <li id="post-5">Item 5</li>
    <li id="post-6">Item 6</li>
  </ul>
  <script>
    const parent = document.getElementById('parent-list');
    parent.addEventListener('click', function(e) {
      const target = e.target;
      if (target.tagName === 'LI') {
        console.log(target.id);
      }
    });
  </script>
</body>

事件委托的使用原因/性能内存问题

在JavaScript中,添加到页面的事件处理程序数量会关系到页面的整体运行性能。两个方面:

  1. 每个函数都是对象,会占用内存,内存中对象越多,性能越差。
  2. 大量事件处理程序导致的DOM访问次数会延迟整个页面的交互就绪时间。

问题

Q1:在事件处理程序的冒泡阶段和捕获阶段,事件处理程序会执行吗?

A:事件处理程序会在捕获阶段和冒泡阶段执行,只要事件处理程序在该阶段注册。

Q2:我可以在同一个元素上同时注册多个相同类型的事件处理程序吗?

A:可以在同一个元素上同时注册多个相同类型的事件处理程序。如果用addEventListener()方法注册多个事件处理程序,所有事件处理程序都会被调用,而不是被最后一个覆盖。如果用事件处理程序属性(如onclick)注册多个事件处理程序,则最后一个覆盖前面的。

Q3:如何在HTML中为一个元素添加事件处理程序?

A:可以使用内联事件处理程序:

html
<button onclick="alert('Hello World!')">Click me!</button>

Q4:如何使用事件对象来获取点击目标的属性?

A:可以通过事件对象的target属性获取:

javascript
btn.addEventListener('click', function(e) {
  console.log(e.target.textContent); // 获取点击目标元素的文本内容
});

Q5:如何停止事件的默认行为?

A:可以使用事件对象的preventDefault()方法:

javascript
btn.addEventListener('click', function(e) {
  e.preventDefault(); // 停止事件的默认行为
  alert('Default behavior stopped!');
});

Released under the MIT License.