jQuery 提供了多种扩展其事件系统的方法,以便在事件绑定到元素时提供自定义功能。在 jQuery 内部,这些扩展主要用于确保标准事件(如 submit 和 change)在不同浏览器中表现一致。此外,它们也可以用于定义具有自定义行为的新事件。
本文档涵盖了从 jQuery 1.7 开始可用的扩展;从 jQuery 1.3 开始,该功能的一个文档较少的子集已经可用,但功能差异很大。有关早期版本中特殊事件的概述,请参阅 Ben Alman 的 jQuery Special Events 文章。
click 或 mouseover 等标准事件的行为可能会导致严重的兼容性问题。link 事件概述与通用建议
编写事件扩展时,理解事件在 jQuery 内部事件系统中的流动至关重要。有关从 API 层面(包括事件委托讨论)对事件系统的描述,请参阅 .on() 方法。
为了简化事件管理,jQuery 针对每个元素的每个事件类型仅附加一个事件处理程序(在符合 W3C 标准的浏览器上使用 addEventListener,在旧版 IE 上使用 attachEvent),然后将其分发给通过 jQuery API 附加的事件处理程序。例如,如果一个元素附加了三个“click”事件处理程序,jQuery 会在附加第一个处理程序时附加自己的处理程序,并将用户的事件处理程序添加到一个列表中,以便在事件发生时执行。对于随后的事件处理程序,由于 jQuery 已经调用浏览器附加了唯一的处理程序,它只需将它们添加到自己的内部列表中。反之,当某个特定类型的最后一个事件从元素中移除时,jQuery 会从浏览器中移除自己的事件处理程序。
事件可以是 W3C 定义的 *原生* (native) 事件,由浏览器根据用户点击鼠标按钮或按键等操作触发。它也可以是 *自定义* (custom) 事件,仅由代码通过 jQuery 的 .trigger() 或 .triggerHandler() 方法触发。代码也可以触发原生浏览器事件,这对于模拟用户操作非常方便。
通常,jQuery 并不具备判断某个事件名称是否可由浏览器触发的内在知识。因此默认情况下,当调用 API 添加该事件的处理程序时,jQuery 总是会将该事件附加到浏览器。如果浏览器从未生成该类型的事件,则调用该处理程序的唯一方法就是由 JavaScript 代码触发该事件。虽然将未使用的事件名称附加到浏览器通常没有危害,但可以使用下文描述的特殊事件 setup 钩子来覆盖默认行为。
每当通过 jQuery 从文档中移除元素时,事件系统都会尝试确保从元素中移除事件和相关数据,以防止内存泄漏。(如果不仔细管理,旧版 Internet Explorer 在这些情况下以内存泄漏著称。)如果事件扩展附加了事件或创建了新对象,则应在移除事件时定义 remove 和 teardown 钩子来分离这些对象或清除数据。
jQuery 事件扩展开发者应避免使用在 DOM 环境中具有特殊含义的事件名称。像 "click"、"change" 或 "load" 这样的事件名称具有 W3C 定义的特定语义,因此将它们用作自定义事件可能会导致意外行为。通常,只有当扩展是为了规范化跨浏览器行为时,才应将 jQuery 事件扩展用于 W3C 定义的事件名称。避免自定义事件冲突的一个常见惯例是在事件类型名称中嵌入冒号或短横线,因为 W3C 事件都不使用这些字符。
虽然 jQuery 的事件系统侧重于向 DOM 元素传递 DOM 事件,但 jQuery 方法也可用于在普通对象上绑定和触发事件。例如,它可以作为一个简单的发布/订阅机制。如果扩展被用于 DOM 和普通对象的混合场景,事件扩展的开发者应尽量避免产生不必要的行为。jQuery 检测 DOM 元素的规范方法是检查对象的 elem.nodeType === 1。
link jQuery.event.props: 数组
jQuery 定义了一个 事件对象 (Event object),它代表了事件发生时可用信息的跨浏览器子集。jQuery.event.props 属性是一个字符串名称数组,这些属性在 jQuery 处理 *原生* 浏览器事件时总是会被复制。(代码通过 .trigger() 触发的事件不使用此列表,因为代码可以使用所需的值构造一个 jQuery.Event 对象并使用该对象触发。)
要在此列表中添加属性名称,请使用 jQuery.event.props.push( "newPropertyName" )。但是请注意,jQuery 处理的每个事件现在都会尝试从原生浏览器事件中将该属性名称复制到 jQuery 构造的事件中。如果该事件类型不存在该属性,它将获得 undefined 值。在此列表中添加过多属性会显著降低事件传递性能,因此对于不常用的属性,直接从 event.originalEvent 中获取值会更高效。如果必须复制属性,强烈建议从 1.7 版本开始使用 jQuery.event.fixHooks。
link jQuery.event.fixHooks: 对象
fixHooks 接口提供了一种按事件类型扩展或规范化 jQuery 在处理 *原生* 浏览器事件时创建的事件对象的方法。fixHooks 条目是一个具有两个属性的对象,每个属性都是可选的:
props: 数组:表示应从浏览器事件对象复制到 jQuery 事件对象的属性字符串。如果省略,则除了 jQuery 复制并规范化的标准属性(例如 event.target 和 event.relatedTarget)之外,不会复制其他属性。
filter: 函数( event, originalEvent ):jQuery 在构造 jQuery.Event 对象、从 jQuery.event.props 复制标准属性以及复制上面指定的 fixHooks 特定属性(如果有)后调用此函数。该函数可以在事件对象上创建新属性或修改现有属性。第二个参数是浏览器的原生事件对象,在 event.originalEvent 中也可以找到它。
请注意,对于所有事件,浏览器的原生事件对象都可以在 event.originalEvent 中找到;如果 jQuery 事件处理程序检查那里的属性而不是 jQuery 规范化的 event 对象,则无需创建 fixHooks 条目来复制或修改属性。
例如,要为 "drop" 事件设置一个复制 dataTransfer 属性的钩子,请为 jQuery.event.fixHooks.drop 分配一个对象:
|
1
2
3
|
|
由于 fixHooks 是一项高级功能且极少在外部使用,jQuery 不包含处理冲突解决的代码或接口。如果某些其他代码可能会将 fixHooks 分配给相同的事件,代码应检查是否存在现有钩子并采取适当措施。一个简单的解决方案可能如下所示:
|
1
2
3
4
5
6
7
|
|
当已知有不同插件想要附加到 drop 钩子的情况时,这种解决方案可能更合适:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
|
link 特殊事件钩子 (Special event hooks)
jQuery 特殊事件钩子是一组按事件名称定义的函数和属性,允许代码控制 jQuery 内部事件处理的行为。该机制与 fixHooks 类似,特殊事件信息存储在 jQuery.event.special.NAME 中,其中 NAME 是特殊事件的名称。事件名称区分大小写。
与 fixHooks 一样,特殊事件钩子的设计假设两个无关的代码片段想要处理相同事件名称的情况非常罕见。需要修改已有钩子事件的特殊事件作者需要采取预防措施,避免通过覆盖这些钩子引入不必要的副作用。
link noBubble: 布尔值
指示调用 .trigger() 方法时此事件类型是否应冒泡;默认情况下为 false,意味着触发的事件将冒泡到元素的父级直到 document(如果附加到 document),然后到 window。请注意,在事件上定义 noBubble 将有效地阻止该事件通过 .trigger() 用于委托事件。
link bindType: 字符串, delegateType: 字符串
定义后,这些字符串属性指定特殊事件在交付之前应像另一种事件类型一样处理。如果事件是直接绑定的,则使用 bindType;对于委托事件,则使用 delegateType。这些类型通常是 DOM 事件类型,且 *不应* 本身是一个特殊事件。
这些属性的行为通过示例最容易理解。假设一个特殊事件定义如下:
|
1
2
3
4
|
|
定义这些属性后,jQuery 事件系统中会发生以下行为:
- "pushy" 事件的处理程序实际上被附加到了 "click" 上——无论是直接绑定还是委托事件。
- "click" 的特殊事件钩子(如果存在)会被调用,*除非* 存在 "pushy" 的
handle钩子,这种情况下在事件交付时会调用它。 - "pushy" 的事件处理程序必须使用 "pushy" 事件名称移除,并且如果从相同元素移除 "click" 事件,则不受影响。
根据上述特殊事件,这段代码表明 pushy 不会因为移除 click 而被移除。例如,这可能是防御不规范插件(其在移除 click 事件时未使用命名空间)的有效方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
|
这两个属性通常与 handle 钩子函数结合使用;例如,该钩子可以在调用事件处理程序之前将事件名称从 "click" 更改为 "pushy"。请参阅下文示例。
link handleObj 对象
下面的许多特殊事件钩子函数都会被传递一个 handleObj 对象,该对象提供了有关事件、它是如何绑定的以及其当前状态的更多信息。此对象及其内容应被视为只读数据,只有以下属性被记录供特殊事件处理程序使用。在下面的讨论中,假设使用这段代码绑定了一个事件:
|
1
2
3
|
|
type: 字符串:事件类型,例如 "click"。当通过 bindType 或 delegateType 使用特殊事件映射时,这将是映射后的类型。
origType: 字符串:原始类型名称(在本例中为 "click"),无论是否通过 bindType 或 delegateType 进行了映射。因此,当 "pushy" 事件映射到 "click" 时,其 origType 将是 "pushy"。详情请参阅上面那些特殊事件属性中的示例。
namespace: 字符串:绑定事件时提供的命名空间(如果有),例如 "myPlugin"。当给定多个命名空间时,它们以句点分隔并按字母升序排序。如果没有提供命名空间,则此属性为空字符串。
selector: 字符串:对于委托事件,这是用于过滤后代元素并确定是否应调用处理程序的选择器。在示例中为 "button"。对于直接绑定的事件,此属性为 null。
data: 对象:在事件绑定期间传递给 jQuery 的数据(如果有),例如 { myData: 42 }。如果省略数据参数或为 undefined,则此属性也是 undefined。
handler: 函数( event: jQuery.Event ):在事件绑定期间传递给 jQuery 的事件处理函数;在示例中,它是对 myHandler 的引用。如果在事件绑定期间传递了 false,则该处理程序指向一个仅返回 false 的单个共享函数。
link setup: 函数( data: Object, namespaces, eventHandle: function )
当特定类型的事件第一次附加到元素时,会调用 setup 钩子;这为钩子提供了一个处理机会,该处理将应用于此元素上此类型的所有事件。this 关键字将是对正在附加事件的元素的引用,而 eventHandle 是 jQuery 的事件处理函数。在大多数情况下,不应使用 namespaces 参数,因为它仅代表被附加的 *第一个* 事件的命名空间;随后的事件可能不具有相同的命名空间。
此钩子可以执行其所需的任何处理,包括将其自己的事件处理程序附加到该元素或其他元素,并使用 jQuery.data() 方法在元素上记录设置信息。如果 setup 钩子希望 jQuery 添加浏览器事件(通过 addEventListener 或 attachEvent,取决于浏览器),它应返回 false。在所有其他情况下,jQuery 将不会添加浏览器事件,但会继续进行该事件的所有其他簿记工作。例如,如果事件从未由浏览器触发而是由 .trigger() 调用,这将是合适的。要在 setup 钩子中附加 jQuery 事件处理程序,请使用 eventHandle 参数。
link teardown: 函数()
当某种特定类型的最后一个事件从元素中移除时,会调用 teardown 钩子。this 关键字将是对正在清理事件的元素的引用。如果该钩子希望 jQuery 从浏览器的事件系统中移除事件(通过 removeEventListener 或 detachEvent),它应返回 false。在大多数情况下,setup 和 teardown 钩子应返回相同的值。
如果 setup 钩子通过 jQuery.data() 等机制附加了事件处理程序或向元素添加了数据,则 teardown 钩子应反转该过程并将其移除。当元素完全从文档中移除时,jQuery 通常会移除数据和事件,但如果元素留在文档中,未能在 teardown 时移除数据或事件将导致内存泄漏。
link add: 函数( handleObj )
每当通过 .on() 等 API 向元素添加事件处理程序时,jQuery 都会调用此钩子。this 关键字将是正在添加事件处理程序的元素,handleObj 参数如上节所述。此钩子的返回值将被忽略。
link remove: 函数( handleObj )
当使用 .off() 等 API 从元素中移除事件处理程序时,会调用此钩子。this 关键字将是正在移除处理程序的元素,handleObj 参数如上节所述。此钩子的返回值将被忽略。
link trigger: 函数( event: jQuery.Event, data: Object )
当使用 .trigger() 或 .triggerHandler() 方法从代码中触发特殊类型的事件(而非源自浏览器的事件)时调用。this 关键字将是正在被触发的元素,event 参数将是根据调用者输入构造的 jQuery.Event 对象。至少会在事件上设置事件类型、数据、命名空间和目标属性。data 参数表示 .trigger() 传递的额外数据(如果存在)。
trigger 钩子在触发事件过程的早期被调用,就在 jQuery.Event 对象构造之后且任何处理程序被调用之前。它可以以任何方式处理触发的事件,例如在返回之前调用 event.stopPropagation() 或 event.preventDefault()。如果钩子返回 false,jQuery 不再执行任何进一步的事件触发操作并立即返回。否则,它将执行正常的触发处理,调用元素的任何事件处理程序并冒泡事件(除非提前停止传播或为该特殊事件指定了 noBubble),以调用附加到父元素的事件处理程序。
link _default: 函数( event: jQuery.Event, data: Object )
当 .trigger() 方法运行完一个事件的所有事件处理程序后,它还会查找并运行目标对象上同名的任何方法,除非处理程序调用了 event.preventDefault()。因此,.trigger( "submit" ) 将执行元素上的 submit() 方法(如果存在)。当指定了 _default 钩子时,该钩子会在检查并执行元素的默认方法之前被调用。如果此钩子返回 false,则将调用元素的默认方法;否则不调用。
link handle: 函数( event: jQuery.Event, data: Object )
当事件发生且 jQuery 通常会调用由 .on() 或其他事件绑定方法指定的用户事件处理程序时,jQuery 会调用 handle 钩子。如果该钩子存在,jQuery 将调用它 *而不是* 那个事件处理程序,并将事件以及从 .trigger() 传递的任何数据(如果不是原生事件)传递给它。this 关键字是正在处理的 DOM 元素,event.handleObj 属性包含详细的事件信息。
根据其拥有的信息,handle 钩子应决定是否调用 event.handleObj.handler 中的原始处理函数。它可以在调用原始处理程序之前修改事件对象中的信息,但 *必须在返回之前恢复* 该数据,否则随后的无关事件处理程序可能会产生不可预测的行为。在大多数情况下,handle 钩子应返回原始处理程序的结果,但这取决于钩子的自由裁量。handle 钩子的独特之处在于,当使用 bindType 和 delegateType 映射类型时,它是唯一在其原始特殊事件名称下被调用的特殊事件函数钩子。出于这个原因,如果特殊事件定义了 bindType 和 delegateType,那么除了 handle 钩子之外还有其他钩子几乎总是一个错误,因为那些其他钩子永远不会被调用。
link 示例:多击 (Multiclick) 事件
这个 multiclick 特殊事件将其自身映射到标准的 click 事件,但使用了 handle 钩子,以便它可以监控事件,并仅在用户点击元素的次数是事件绑定期间指定次数的倍数时才交付事件。
该钩子在数据对象中存储当前的点击计数,因此不同元素上的 multiclick 处理程序不会相互干扰。它在调用处理程序之前将事件类型更改为原始的 "multiclick" 类型,并在返回之前将其恢复为映射后的 "click" 类型:
|
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
|
|