发布在:事件

事件简介

link 简介

网页都是关于交互的。用户执行无数操作,例如将鼠标移到页面上、单击元素和在文本框中输入文本——所有这些都是事件的示例。除了这些用户事件之外,还发生许多其他事件,例如当页面加载时、当视频开始播放或暂停时等。每当页面上发生一些有趣的事情时,就会触发一个事件,这意味着浏览器基本上会宣布某事已发生。正是这个公告允许开发人员“监听”事件并适当地对它们做出反应。

link 什么是 DOM 事件?

如前所述,有无数的事件类型,但也许最容易理解的是用户事件,例如当有人单击元素或在表单中输入内容时。这些类型的事件发生在元素上,这意味着当用户单击按钮时,例如,按钮上发生了事件。虽然用户交互并不是 DOM 事件的唯一类型,但它们肯定是最容易理解的,特别是刚开始的时候。Mozilla Developer Network 对可用的 DOM 事件进行了很好的参考。

link 监听事件的方法

有很多方法可以监听事件。网页上不断发生操作,但只有当开发人员监听它们时,才会收到它们的通知。监听事件基本上意味着您正在等待浏览器告诉您已发生特定事件,然后您将指定页面应如何做出反应。

要向浏览器指定在事件发生时要做什么,您需要提供一个函数,也称为事件处理程序。每当事件发生(或直到事件解除绑定)时,都会执行此函数。

例如,要每当用户单击按钮时发出警报,你可以写类似这样的内容

1
<button onclick="alert('Hello')">Say hello</button>

我们要监听的事件由按钮的onclick属性指定,事件处理程序是alert函数,它向用户发出“Hello”警报。虽然这有效,但由于以下几个原因,这是实现此功能的一种糟糕方法

  1. 首先,我们将视图代码(HTML)与交互代码(JS)耦合在一起。这意味着每当我们需要更新功能时,我们都必须编辑 HTML,这是一种不好的做法,也是维护的噩梦。
  2. 其次,它不可扩展。如果你必须将此功能附加到多个按钮上,你不仅会用一堆重复的代码使页面臃肿,而且还会再次破坏可维护性。

像这样使用内联事件处理程序可以被认为是侵入式 JavaScript,但它的反面,非侵入式 JavaScript是讨论该主题更常见的方式。非侵入式 JavaScript的概念是你的 HTML 和 JS 是分开的,因此更易于维护。关注点分离很重要,因为它将类似的代码片段(即 HTML、JS、CSS)放在一起,并将不同的代码片段分开,从而方便更改、增强等。此外,非侵入式 JavaScript 强调尽可能减少向页面添加的冗余代码。如果用户的浏览器不支持 JavaScript,那么它不应该与页面的标记交织在一起。此外,为了防止命名冲突,JS 代码应为不同的功能或库使用单个命名空间。jQuery 就是一个很好的例子,因为jQuery对象/构造函数(以及$别名到jQuery)只使用一个全局变量,并且 jQuery 的所有功能都打包到该一个对象中。

为了以非侵入式的方式完成所需的任务,让我们通过删除onclick属性并用一个id替换它来稍微更改我们的 HTML,我们将在脚本文件中使用该id“挂钩”到按钮。

1
<button id="helloBtn">Say hello</button>

如果我们希望在用户以非侵入式方式单击该按钮时收到通知,我们可以在单独的脚本文件中执行类似以下操作

1
2
3
4
5
6
// Event binding using addEventListener
var helloBtn = document.getElementById( "helloBtn" );
helloBtn.addEventListener( "click", function( event ) {
alert( "Hello." );
}, false );

在这里,我们通过调用 getElementById 并将其返回值分配给变量来保存对按钮元素的引用。然后,我们调用 addEventListener 并提供一个事件处理程序函数,该函数将在该事件发生时被调用。虽然这段代码没有问题,因为它在现代浏览器中可以正常工作,但在 IE9 之前的 IE 版本中却无法正常工作。这是因为 Microsoft 选择实现不同的方法 attachEvent,而不是 W3C 标准 addEventListener,并且直到 IE9 发布后才改变了它。因此,利用 jQuery 是有益的,因为它消除了浏览器的不一致性,允许开发人员使用单个 API 来执行这些类型的任务,如下所示。

1
2
3
4
// Event binding using a convenience method
$( "#helloBtn" ).click(function( event ) {
alert( "Hello." );
});

$( "#helloBtn" ) 代码使用 $(又名 jQuery)函数选择按钮元素并返回 jQuery 对象。jQuery 对象有很多可用的方法(函数),其中一个名为 click,它位于 jQuery 对象的原型中。我们在 jQuery 对象上调用 click 方法并传递一个匿名函数事件处理程序,当用户单击按钮时该处理程序将被执行,向用户发出“Hello”。

可以使用 jQuery 侦听事件的方法有很多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// The many ways to bind events with jQuery
// Attach an event handler directly to the button using jQuery's
// shorthand `click` method.
$( "#helloBtn" ).click(function( event ) {
alert( "Hello." );
});
// Attach an event handler directly to the button using jQuery's
// `bind` method, passing it an event string of `click`
$( "#helloBtn" ).bind( "click", function( event ) {
alert( "Hello." );
});
// As of jQuery 1.7, attach an event handler directly to the button
// using jQuery's `on` method.
$( "#helloBtn" ).on( "click", function( event ) {
alert( "Hello." );
});
// As of jQuery 1.7, attach an event handler to the `body` element that
// is listening for clicks, and will respond whenever *any* button is
// clicked on the page.
$( "body" ).on({
click: function( event ) {
alert( "Hello." );
}
}, "button" );
// An alternative to the previous example, using slightly different syntax.
$( "body" ).on( "click", "button", function( event ) {
alert( "Hello." );
});

从 jQuery 1.7 开始,所有事件都通过 on 方法绑定,无论您直接调用它还是使用别名/快捷方式方法(例如 bindclick),这些方法在内部都映射到 on 方法。考虑到这一点,使用 on 方法是有益的,因为其他方法都只是语法糖,而使用 on 方法将产生更快、更一致的代码。

让我们看看上面的 on 示例并讨论它们的差异。在第一个示例中,将字符串 click 作为第一个参数传递给 on 方法,并将匿名函数作为第二个参数传递。这看起来很像之前的 bind 方法。在这里,我们将事件处理程序直接附加到 #helloBtn。如果页面上还有其他按钮,当单击这些按钮时,它们不会发出“Hello”提示,因为该事件仅附加到 #helloBtn

在第二个 on 示例中,我们传递了一个对象(由大括号 {} 表示),它有一个属性 click,其值是一个匿名函数。on 方法的第二个参数是一个 jQuery 选择器字符串 button。虽然示例 1-3 在功能上是等效的,但示例 4 不同,因为 body 元素正在侦听发生在任何按钮元素上的单击事件,而不仅仅是 #helloBtn。上面的最后一个示例与前一个示例完全相同,但我们传递的是一个事件字符串、一个选择器字符串和回调,而不是传递一个对象。这两个示例都是事件委托的示例,事件委托是一个元素在 DOM 树中侦听其子元素上发生的事件的过程。

link 事件委托

事件委托之所以有效,是因为事件冒泡的概念。对于大多数事件,每当页面上发生某些事情(例如单击某个元素)时,事件就会从发生事件的元素传播到其父元素,然后传播到父元素的父元素,依此类推,直到到达根元素,即 window。因此,在我们的表格示例中,每当单击 td 时,其父元素 tr 也会收到单击通知,父元素 table 会收到通知,body 会收到通知,最终 window 也会收到通知。虽然事件冒泡和委托运行良好,但委托元素(在我们的示例中,即 table)应始终尽可能靠近被委托元素,以便事件不必在调用其处理程序函数之前在 DOM 树中向上传播很远。

事件委托相对于直接绑定到元素(或一组元素)的两个主要优点是性能和前面提到的事件冒泡。想象一下有一个包含 1000 个单元格的大表格,并为每个单元格绑定一个事件。这是 1000 个单独的事件处理程序,浏览器必须附加这些处理程序,即使它们都映射到同一个函数。但是,我们不必绑定到每个单独的单元格,而是可以使用委托来侦听发生在父表格上的事件并做出相应反应。将绑定一个事件而不是 1000 个事件,从而大大提高性能和内存管理。

发生的事件冒泡使我们能够通过 Ajax 添加单元格,例如,而不必将事件直接绑定到这些单元格,因为父表正在侦听点击并因此收到其子项上的点击通知。如果我们不使用委托,我们将不得不为每个添加的单元格不断绑定事件,这不仅是一个性能问题,而且还可能成为维护噩梦。

链接 事件对象

在所有先前的示例中,我们一直在使用匿名函数并在该函数中指定一个 event 参数。让我们稍作更改。

1
2
3
4
5
6
// Binding a named function
function sayHello( event ) {
alert( "Hello." );
}
$( "#helloBtn" ).on( "click", sayHello );

在这个稍有不同的示例中,我们正在定义一个名为 sayHello 的函数,然后将该函数传递到 on 方法中,而不是匿名函数。如此多的在线示例显示了用作事件处理程序的匿名函数,但重要的是要认识到,您还可以将已定义的函数作为事件处理程序传递。如果不同的元素或不同的事件应执行相同的功能,这一点很重要。这有助于保持代码 DRY

但是 sayHello 函数中的 event 参数是什么——它是什么,为什么它很重要?在所有 DOM 事件回调中,jQuery 传递一个 事件对象 参数,其中包含有关事件的信息,例如它发生的准确时间和地点、它是什么类型的事件、事件发生在哪个元素上以及大量其他信息。当然,您不必称它为 event;您可以称它为 e 或您想要的任何名称,但 event 是一个非常常见的惯例。

如果元素具有特定事件的默认功能(例如,链接打开新页面,表单中的按钮提交表单等),则可以取消该默认功能。这通常对 Ajax 请求很有用。当用户单击按钮通过 Ajax 提交表单时,我们希望取消按钮/表单的默认操作(将其提交到表单的 action 属性),而我们改为执行 Ajax 请求以完成同一任务,以获得更无缝的体验。为此,我们将利用事件对象并调用其 .preventDefault() 方法。我们还可以使用 .stopPropagation() 阻止事件在 DOM 树中冒泡,以便父元素不会收到其发生通知(在使用事件委托的情况下)。

1
2
3
4
5
6
7
8
9
10
11
// Preventing a default action from occurring and stopping the event bubbling
$( "form" ).on( "submit", function( event ) {
// Prevent the form's default submission.
event.preventDefault();
// Prevent event from bubbling up DOM tree, prohibiting delegation
event.stopPropagation();
// Make an AJAX request to submit the form data
});

同时使用 .preventDefault().stopPropagation() 时,您可以改为 return false 以更简洁的方式实现两者,但建议仅在两者都实际需要时才 return false,而不仅仅是为了简洁。关于 .stopPropagation() 的最后一点说明是,在委托事件中使用它时,最早可以停止事件冒泡的时间是事件到达委托它的元素时。

同样重要的是要注意,事件对象包含一个名为 originalEvent 的属性,它是浏览器本身创建的事件对象。jQuery 用一些有用的方法和属性包装了这个本机事件对象,但在某些情况下,您需要通过 event.originalEvent 访问原始事件。这对于移动设备和平板电脑上的触摸事件特别有用。

最后,要检查事件本身并查看它包含的所有数据,您应该使用 console.log 在浏览器的控制台中记录事件。这将允许您查看事件的所有属性(包括 originalEvent),这对于调试非常有帮助。

1
2
3
4
5
6
7
8
9
10
11
// Logging an event's information
$( "form" ).on( "submit", function( event ) {
// Prevent the form's default submission.
event.preventDefault();
// Log the event object for inspectin'
console.log( event );
// Make an AJAX request to submit the form data
});