是否应该将所有jquery事件都绑定到$(document)?


163

这是哪里来的

当我第一次学习jQuery时,通常会附加如下事件:

$('.my-widget a').click(function() {
    $(this).toggleClass('active');
});

在学习了更多关于选择器速度和事件委托的知识之后,我在几个地方读到“ jQuery事件委托将使您的代码更快。” 所以我开始写这样的代码:

$('.my-widget').on('click','a',function() {
    $(this).toggleClass('active');
});

这也是复制不推荐使用的.live()事件的行为的推荐方法。这对我来说很重要,因为我的许多站点始终都在动态添加/删除小部件。上面的行为与.live()并不完全一样,因为只有添加到已经存在的容器'.my-widget'中的元素才能获得该行为。如果在该代码运行后动态添加另一个html块,则这些元素将不会获得绑定到它们的事件。像这样:

setTimeout(function() {
    $('body').append('<div class="my-widget"><a>Click does nothing</a></div>');
}, 1000);


我要实现的目标:

  1. .live()的旧行为//表示将事件附加到尚不存在的元素上
  2. .on()的好处
  3. 绑定事件的最快性能
  4. 管理事件的简单方法

我现在附上所有这样的事件:

$(document).on('click.my-widget-namespace', '.my-widget a', function() {
    $(this).toggleClass('active');
});

这似乎可以满足我的所有目标。(是的,由于某种原因,它在IE中的运行速度较慢,不知道为什么吗?)之所以快,是因为只有单个事件绑定到单个元素,并且仅在事件发生时才评估辅助选择器(如果此处有误,请更正我)。命名空间很棒,因为它使切换事件监听器更加容易。

我的解决方案/问题

因此,我开始认为jQuery事件应始终绑定到$(document)。
有什么理由为什么您不想这样做?
可以认为这是最佳做法吗?如果没有,为什么?

如果您已阅读了整本书,则谢谢。我感谢任何/所有反馈/见解。

假设:

  1. 使用支持.on()//至少1.7版的jQuery
  2. 您希望将事件添加到动态添加的内容中

阅读/示例:

  1. http://24ways.org/2011/your-jquery-now-with-less-suck
  2. http://brandonaaron.net/blog/2010/03/4/event-delegation-with-jquery
  3. http://www.jasonbuckboyer.com/playground/speed/speed.html
  4. http://api.jquery.com/on/

Answers:


215

否-你应该不绑定所有委托的事件处理程序的document对象。这可能是您可能创建的性能最差的方案。

首先,事件委托并不总是使您的代码更快。在某些情况下,这是有利的,而在某些情况下则不是。当您实际需要事件委托并从中受益时,应该使用事件委托。否则,您应该将事件处理程序直接绑定到事件发生的对象,因为这样通常会更有效。

其次,您不应在文档级别绑定所有委托事件。这正是.live()被弃用的原因,因为当您以这种方式绑定许多事件时,效率非常低。对于委托事件处理,将它们绑定到非动态的最接近的父级要高效得多。

第三,不是所有事件都能正常工作,或者不是所有问题都可以通过委托解决。例如,如果您想拦截输入控件上的键事件并阻止将无效键输入到输入控件中,则不能使用委托事件处理来做到这一点,因为到事件冒泡到委托处理程序时,它已经已由输入控件处理,现在来影响该行为为时已晚。

在某些情况下,需要进行事件委派或有利于进行事件委派:

  • 当您要捕获事件的对象被动态创建/删除时,您仍然希望捕获其上的事件,而不必在每次创建新对象时显式重新绑定事件处理程序。
  • 当您有许多都需要完全相同的事件处理程序的对象时(其中很多至少为数百个)。在这种情况下,在安装时绑定一个委托事件处理程序可能比绑定数百个或更多个直接事件处理程序更有效。注意,委托事件处理在运行时总是比直接事件处理程序效率低。
  • 当您尝试捕获(在文档的更高级别)文档中任何元素上发生的事件。
  • 当您的设计明确使用事件冒泡和stopPropagation()解决页面中的某些问题或功能时。

为了进一步了解这一点,需要了解jQuery委托事件处理程序的工作方式。当您调用类似这样的内容时:

$("#myParent").on('click', 'button.actionButton', myFn);

它将在#myParent对象上安装通用jQuery事件处理程序。当click事件冒泡到此委托事件处理程序时,jQuery必须遍历附加到此对象的委托事件处理程序列表,并查看事件的原始元素是否与委托事件处理程序中的任何选择器匹配。

因为选择器可以相当广泛地参与其中,所以这意味着jQuery必须解析每个选择器,然后将其与原始事件目标的特征进行比较,以查看它是否与每个选择器匹配。这不是一个便宜的操作。如果只有一个选择器,这没什么大不了的,但是,如果将所有选择器放在文档对象上,并且有数百个选择器要与每个冒泡事件进行比较,这会严重影响事件处理性能。

因此,您需要设置委托事件处理程序,以使委托事件处理程序尽可能接近目标对象。这意味着更少的事件将在每个委派的事件处理程序中冒泡,从而提高了性能。将所有委托事件放在文档对象上是最糟糕的性能,因为所有冒泡事件都必须经过所有委托事件处理程序并针对所有可能的委托事件选择器进行评估。这就是为什么.live()不赞成这样.live()做的原因,因为这样做是事实,而且事实证明效率很低。


因此,要实现优化的性能:

  1. 仅在实际提供所需功能或提高性能时才使用委托事件处理。不要总是使用它,因为它很容易,因为当您实际上不需要它时。实际上,它在事件分发时的性能要比直接事件绑定差。
  2. 尽可能将委派的事件处理程序附加到事件源的最接近的父级。如果使用委派事件处理是因为您具有要为其捕获事件的动态元素,请选择本身不是动态的最接近的父级。
  3. 对委托的事件处理程序使用易于评估的选择器。如果您遵循委托事件处理的工作原理,您将了解必须将委托事件处理程序与许多对象进行多次比较,因此,选择效率尽可能高的选择器或向对象添加简单的类,以便可以使用更简单的选择器提高委托事件处理的性能。

2
太棒了 感谢您的快速详细说明。这很有意义,因为使用.on()会增加事件的分配时间,但我认为我仍在努力决定增加事件的分配时间与初始页面处理团队之间的差距。我通常希望减少初始页面时间。例如,如果我在一个页面上有200个元素(并且随着事件的发生而增加),那么在初始加载时(因为它必须添加100个事件侦听器),它的开销大约是100倍,而不是在页面上添加单个事件侦听器父容器链接
巴克

还想说您说服了我-不要将所有事件都绑定到$(document)。尽可能绑定到最接近的不变父对象。我想,当事件委托比直接将事件绑定到元素更好时,我仍然需要根据情况决定。当我需要绑定到动态内容的事件(没有不变的父对象)时,我想我会继续做@Vega下面建议的事情,并简单地“在将内容插入到( )DOM”,在我的选择器中使用jQuery的context参数。
巴克

5
@ user1394692-如果您有很多几乎相同的元素,那么为它们使用委托事件处理程序可能是有意义的。我在回答中说。如果我有一个巨大的500行表,并且在特定列的每一行中都有相同的按钮,那么我将在表上使用一个委派的事件处理程序来服务所有按钮。但是,如果我有200个元素并且它们都需要自己独特的事件处理程序,则安装200个委托事件处理程序并不比安装200个直接绑定事件处理程序快,但是委托事件处理在执行事件时可能会慢很多。
jfriend00 '10 -10-10

你太完美了 完全同意!我知道这是我最糟糕的事情$(document).on('click', '[myCustomAttr]', function () { ... });吗?
Artem Fitiskin 2014年

使用pjax / turbolinks会引起一个有趣的问题,因为所有元素都是动态创建/删除的(除了第一页加载)。因此,它是更好的所有事件,一旦绑定到document结合到更具体的选择page:load和取消绑定这些事件的page:after-remove?Hmmm
Dom Christie

9

事件委托是一种在元素实际存在于DOM中之前编写处理程序的技术。此方法有其自身的缺点,只有在有此类要求时才应使用。

什么时候应该使用事件委托?

  1. 当您为需要相同功能的更多元素绑定通用处理程序时。(例如:表格行悬停)
    • 在该示例中,如果必须使用直接绑定来绑定所有行,那么最终将为该表中的n行创建n个处理程序。通过使用委托方法,您最终可以在一个简单的处理程序中处理所有这些操作。
  2. 当您在DOM中更频繁地添加动态内容时(例如:从表中添加/删除行)

为什么不应该使用事件委托?

  1. 与将事件直接绑定到元素相比,事件委托要慢一些。
    • 它在击中的每个气泡上都比较目标选择器,这种比较昂贵而且复杂。
  2. 在事件冒泡到达它所绑定的元素之前,无法对其进行控制。

PS:即使对于动态内容,如果在将内容插入DOM之后绑定处理程序,则也不必使用事件委托方法。(如果动态添加的内容不经常删除/重新添加)



0

我想在jfriend00的答案中添加一些评论和反对意见。(主要是基于我的直觉,我的意见)

否-不应将所有委托的事件处理程序绑定到文档对象。这可能是您可能创建的性能最差的方案。

首先,事件委托并不总是使您的代码更快。在某些情况下,这是有利的,而在某些情况下则不是。当您实际需要事件委托并从中受益时,应该使用事件委托。否则,您应该将事件处理程序直接绑定到事件发生的对象,因为这样通常会更有效。

如果只为一个元素进行注册和事件化,确实可以提高性能,但我相信它不会与委派带来的可伸缩性优势相提并论。我也相信浏览器正在(将要)越来越有效地处理此问题,尽管我没有证据。我认为,事件委派是必经之路!

其次,您不应在文档级别绑定所有委托事件。这就是为什么不建议使用.live()的原因,因为当您有许多这样绑定的事件时,这效率很低。对于委托事件处理,将它们绑定到非动态的最接近的父级要高效得多。

我对此表示同意。如果您100%确信某个事件只会在容器内发生,则将事件绑定到此容器是有意义的,但是我仍然反对将事件直接绑定到触发元素。

第三,不是所有事件都能正常工作,或者不是所有问题都可以通过委托解决。例如,如果您想拦截输入控件上的键事件并阻止将无效键输入到输入控件中,则不能使用委托事件处理来做到这一点,因为到事件冒泡到委托处理程序时,它已经已由输入控件处理,现在来影响该行为为时已晚。

这是不正确的。请参阅此代码笔:https ://codepen.io/pwkip/pen/jObGmjq

document.addEventListener('keypress', (e) => {
    e.preventDefault();
});

它说明了如何通过在文档上注册keypress事件来防止用户键入内容。

在某些情况下,需要进行事件委派或有利于进行事件委派:

当您要捕获事件的对象被动态创建/删除时,您仍然希望捕获其上的事件,而不必在每次创建新对象时都明确地重新绑定事件处理程序。当您有许多都需要完全相同的事件处理程序的对象时(其中很多至少为数百个)。在这种情况下,在设置时绑定一个委托事件处理程序可能比绑定数百个或更多个直接事件处理程序更有效。注意,委托事件处理在运行时总是比直接事件处理程序效率低。

我想从https://ehsangazar.com/optimizing-javascript-event-listeners-for-performance-e28406ad406c引用此报价

Event delegation promotes binding as few DOM event handlers as possible, since each event handler requires memory. For example, let’s say that we have an HTML unordered list we want to bind event handlers to. Instead of binding a click event handler for each list item (which may be hundreds for all we know), we bind one click handler to the parent unordered list itself.

同样,谷歌搜索performance cost of event delegation google返回更多的结果,有利于事件委托。

当您尝试捕获(在文档的更高级别)文档中任何元素上发生的事件。当您的设计明确使用事件冒泡和stopPropagation()解决页面中的某些问题或功能时。为了进一步了解这一点,需要了解jQuery委托事件处理程序的工作方式。当您调用类似这样的内容时:

$(“#myParent”)。on('click','button.actionButton',myFn); 它将在#myParent对象上安装通用jQuery事件处理程序。当单击事件冒泡到此委托事件处理程序时,jQuery必须遍历附加到此对象的委托事件处理程序列表,并查看事件的原始元素是否与委托事件处理程序中的任何选择器匹配。

因为选择器可以相当广泛地参与其中,所以这意味着jQuery必须解析每个选择器,然后将其与原始事件目标的特征进行比较,以查看它是否与每个选择器匹配。这不是一个便宜的操作。如果只有一个选择器,这没什么大不了的,但是如果将所有选择器放在文档对象上,并且有数百个选择器要与每个冒泡事件进行比较,这会严重影响事件处理性能。

因此,您需要设置委托事件处理程序,以使委托事件处理程序尽可能接近目标对象。这意味着更少的事件将在每个委派的事件处理程序中冒泡,从而提高了性能。将所有委托事件放在文档对象上是最糟糕的性能,因为所有冒泡事件都必须经过所有委托事件处理程序,并针对所有可能的委托事件选择器进行评估。这就是为什么不推荐使用.live()的原因,因为这是.live()所做的,而且事实证明效率很低。

这在哪里记录?如果是这样,那么jQuery似乎以一种非常低效的方式处理委托,那么我的反论点应仅适用于原始JS。

尽管如此:我想找到支持此说法的官方消息。

::编辑::

似乎jQuery确实确实以非常低效的方式进行事件冒泡(因为它们支持IE8) https://api.jquery.com/on/#event-performance

因此,我在这里的大多数论证仅适用于香草JS和现代浏览器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.