【问题标题】:Should all jquery events be bound to $(document)?所有 jquery 事件都应该绑定到 $(document) 吗?
【发布时间】:2012-10-01 05:33:28
【问题描述】:

这是从哪里来的

当我第一次学习 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()的jQuery //至少1.7版本
  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/

【问题讨论】:

标签: javascript jquery jquery-selectors jquery-on


【解决方案1】:

不 - 您不应该将所有委托的事件处理程序绑定到 document 对象。这可能是您可以创建的性能最差的场景。

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

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

第三,并非所有事件都有效或所有问题都可以通过委派解决。例如,如果您想拦截输入控件上的键事件并阻止将无效键输入到输入控件中,则无法通过委托事件处理来实现,因为当事件冒泡到委托处理程序时,它已经已由输入控件处理,现在影响该行为为时已晚。

以下是需要或有利的事件委托的时候:

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

要进一步理解这一点,需要了解 jQuery 委托事件处理程序的工作原理。当你这样调用时:

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

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

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

因此,您需要设置委托事件处理程序,以便委托事件处理程序尽可能接近目标对象。这意味着更少的事件将通过每个委托的事件处理程序冒泡,从而提高性能。将所有委托事件放在文档对象上是最差的性能,因为所有冒泡事件都必须通过所有委托事件处理程序并针对所有可能的委托事件选择器进行评估。这正是 .live() 被弃用的原因,因为这是 .live() 所做的,并且被证明效率非常低。


所以,要达到优化的性能:

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

【讨论】:

  • 太棒了。感谢您快速详细的描述。使用 .on() 会增加事件分派时间是有道理的,但我认为我仍在努力在增加的事件分派时间与初始页面处理团队之间做出决定。我通常会减少初始页面时间。例如,如果我在一个页面上有 200 个元素(事件发生时会更多),那么在初始加载时它的成本大约是 100 倍(因为它必须添加 100 个事件侦听器),而不是我添加一个单一的事件侦听器到父容器link
  • 还想说你说服了我 - 不要将所有事件绑定到 $(document)。尽可能绑定到最近的不变父级。我想当事件委托比直接将事件绑定到元素更好时,我仍然需要根据具体情况做出决定。当我需要与动态内容相关的事件(没有不变的父级)时,我想我会继续做@Vega 下面推荐的操作,并简单地“在内容插入后绑定处理程序( ) DOM",在我的选择器中使用 jQuery 的上下文参数。
  • @user1394692 - 如果您有很多几乎相同的元素,那么为它们使用委托事件处理程序可能是有意义的。我在回答中这么说。如果我有一个巨大的 500 行表格,并且在特定列的每一行中都有相同的按钮,我会在表格上使用一个委托事件处理程序来处理所有按钮。但是如果我有 200 个元素并且它们都需要自己独特的事件处理程序,那么安装 200 个委托事件处理程序并不比安装 200 个直接绑定的事件处理程序快,但是在事件执行时委托事件处理可能会慢很多。跨度>
  • 你是完美的。完全同意!我是否明白这是我能做的最糟糕的事情 - $(document).on('click', '[myCustomAttr]', function () { ... });
  • 使用 pjax/turbolinks 带来了一个有趣的问题,因为所有元素都是动态创建/删除的(除了第一个页面加载)。那么将所有事件绑定到document 绑定到page:load 上的更具体的选择,然后在page:after-remove 上取消绑定这些事件,是不是更好?嗯
【解决方案2】:

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

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

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

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

  1. 与将事件直接绑定到元素相比,事件委托更慢。
    • 它会比较它击中的每个气泡上的目标选择器,这种比较既复杂又昂贵。
  2. 无法控制事件冒泡,直到它碰到它所绑定的元素。

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

【讨论】:

    【解决方案3】:

    显然,现在实际上建议使用事件委托。至少对于香草 js。

    https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/

    "网络性能# 感觉就像监听文档中的每一次点击都会影响性能,但实际上它比在单个项目上拥有一堆事件监听器更高效。”

    【讨论】:

      【解决方案4】:

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

      不 - 您不应该将所有委托的事件处理程序绑定到文档 目的。这可能是您可能遇到的最糟糕的情况 创建。

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

      虽然如果您只为单个元素注册和事件,性能可能会稍好一些,但我相信它并不能与委托带来的可扩展性优势相提并论。我也相信浏览器(将会)越来越有效地处理这个问题,尽管我没有证据证明这一点。在我看来,事件委托是要走的路!

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

      我有点同意这一点。如果您 100% 确定事件只会发生在容器内,那么将事件绑定到该容器是有意义的,但我仍然反对直接将事件绑定到触发元素。

      第三,并非所有事件都有效或所有问题都可以解决 代表团。例如,如果你想拦截一​​个关键事件 输入控制和阻止无效键被输入到输入 控制,你不能通过委托事件处理来做到这一点,因为 事件冒泡到委托处理程序的时间,它已经 已被输入控件处理,影响已来不及 这种行为。

      这根本不是真的。请看这个codePen:https://codepen.io/pwkip/pen/jObGmjq

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

      它说明了如何通过在文档上注册按键事件来防止用户打字。

      以下是需要或有利的事件委托的时候:

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

      我想回复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 似乎以一种非常低效的方式处理委托,那么我的反驳应该只适用于 vanilla JS。

      Still:我想找到支持这一说法的官方消息来源。

      :: 编辑 ::

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

      所以我在这里的大部分论点只适用于 vanilla JS 和现代浏览器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-01
        • 2020-03-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多