【问题标题】:Most efficient way to dynamically bind event handlers动态绑定事件处理程序的最有效方法
【发布时间】:2014-10-22 13:15:08
【问题描述】:

问题:我需要在动态运行时将任意数量的事件处理程序绑定到任意数量的元素(DOM 节点、windowdocument),并且我需要能够更新事件在我的页面的生命周期内绑定动态创建(或销毁)的节点。我可以看到解决此问题的三个选项:

I) window上的事件委托
II) 在每个节点上直接绑定事件
III) 共同祖先的事件委托(直到运行时才知道,并且可能需要在 DOM 更改时重新计算)

最有效的方法是什么?

一点背景

我正在处理一组需要对用户事件(点击、滚动等)进行分析跟踪的页面,并且我希望能够在一堆页面中轻松配置这些事件处理程序,而无需编写脚本处理每个实例的事件绑定。此外,因为我可能需要在未来跟踪新事件,或者跟踪动态添加到页面/从页面中删除的元素上的事件,所以我需要能够考虑到生命周期中发生的 DOM 变化页面。

作为我目前正在考虑的一个例子,我想创建一个接受配置对象的函数,该对象允许程序员为每个事件指定默认处理程序,并允许他们为特定元素覆盖它们:

Analytics.init({
    // default handlers for each event type
    defaultHandlers: {
        "click": function(e) { ... },
        "focus": function(e) { ... }
    },

    // elements to listen to 
    targetElements: {

        // it should work with non-DOM nodes like 'window' and 'document'
        window: {
            // events for which the default handlers should be called
            useDefaultHandlers: ['click'],

            // custom handler
            "scroll": function(e) { ... }
        },

        // it should work with CSS selectors
        "#someId": {
            useDefaultHandlers: ['click', 'focus'],
            "blur": function(e) { ... }
        }
    }
});

来源

【问题讨论】:

  • 我认为这取决于。如果你想处理大多数到达document 的事件,你可以委托给它。但是如果你只处理一小部分,特别是如果事件触发很多(例如mousemovescroll),最好委托给更近的祖先。如果有很多元素,我不会绑定到每个元素。
  • Backbone.js 有一种非常优雅的方法。见View-delegateEvents

标签: javascript jquery google-analytics dom-events event-delegation


【解决方案1】:

我通常在 document.documentElement 对象上委托事件,因为:

  1. 它代表页面上的<html>元素,包含所有内容包含用户可以与之交互的所有HTML标签。
  2. 它可以在 JavaScript 开始执行时使用,无需窗口加载或 DOM 就绪事件处理程序
  3. 您仍然可以捕获“滚动”事件

至于事件委托的效率,事件必须冒泡的节点越多,花费的时间就越长,但是我们说的是大约 1 到 2 毫秒的时间差 -- 也许。用户无法察觉。导致性能损失的通常是 DOM 事件的处理,而不是事件从一个节点到另一个节点的冒泡。

我发现以下因素总体上会对 JavaScript 性能产生负面影响:

  1. 文档树中的节点越多,浏览器操作它的时间就越长。
  2. 页面上的事件处理程序数量越多,JavaScript 的速度就越慢,尽管您需要 100 多个处理程序才能真正看到差异。

主要是,#1 的影响最大。我认为在大多数情况下,试图提高事件处理的性能是一种过早的优化。我看到的优化事件处理代码的唯一情况是当您有一个每秒触发多次的事件(例如“scroll”和“mousemove”事件)。事件委托的额外好处是您不必清理 DOM 节点上的事件处理程序,这些节点将与文档树分离,从而允许浏览器垃圾收集该内存。

(来自下面的 cmets)wvandell 说:

事件委托的性能成本与事件的实际“冒泡”几乎没有关系......将许多选择器委托给单个父级时会产生性能损失。

这是真的,但是让我们考虑一下感知到的性能。委派许多点击事件不会引起用户注意。如果您委托scrollmousemove 之类的事件,该事件每秒可以触发50 次以上(留出20 毫秒来处理事件),那么用户可能会察觉到性能问题。这又回到了我反对过早优化事件处理程序代码的论点。

许多点击事件可以毫无问题地委托给一个共同的祖先,例如document.documentElement。我会在那里委托一个“mousemove”事件吗? 也许吧。这取决于其他情况以及委托的“mousemove”事件是否足够灵敏。

【讨论】:

  • 您说:“它代表页面上的 元素,它包含所有内容”。这并不完全正确,因为documentwindow 不在<html> 元素内。如果您将所有事件处理委托给document.documentElement,您将错过popstatescrollDOMContentLoaded 等事件。
  • @GregBurghardt 我认为您所写的某些内容错过了问题的重点和/或歪曲了事件委托的现实。事件委托的性能成本与事件的实际“冒泡”几乎没有关系(正如您正确指出的那样)。但是,将许多选择器委托给单个父级时会产生性能损失。来自 jQuery .on() docs:
  • 在文档树顶部附近附加许多委托事件处理程序会降低性能。每次事件发生时,jQuery 必须将该类型的所有附加事件的所有选择器与从事件目标到文档顶部的路径中的每个元素进行比较。为了获得最佳性能,请将委托事件附加到尽可能靠近目标元素的文档位置。避免过度使用 document 或 document.body 来处理大型文档的委托事件。
  • @wvandall:所以 jQuery 是性能瓶颈,而不是在 <html> 元素上委派事件。
  • @PhilipWalton:我应该编辑我的帖子。我非常松散地使用“拥有一切”这个词。它包含用户可以与之交互的所有 HTML 标签。
猜你喜欢
  • 1970-01-01
  • 2020-06-03
  • 2016-12-10
  • 1970-01-01
  • 2011-12-14
  • 2012-07-27
  • 1970-01-01
  • 2011-10-11
  • 1970-01-01
相关资源
最近更新 更多