动态绑定事件处理程序的最有效方法

8
问题:我需要在运行时动态地将任意数量的事件处理程序绑定到任意数量的元素(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) { ... }
        }
    }
});

参考资料

注意:请勿删除HTML标签。

我认为这取决于情况。如果你想处理到达document的大多数事件,那么你可以委托给它。但是,如果你只处理一小部分事件,特别是如果这些事件频繁触发(例如mousemovescroll),最好委托给更近的祖先元素。如果有很多元素,我不会对每个元素进行绑定。 - Oriol
Backbone.js有一种非常优雅的实现方式。请参见View-delegateEvents - tcooc
1个回答

1

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

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

至于事件委托的效率,事件必须冒泡到更多节点的时间越长,但我们谈论的是1到2毫秒的时间差 - 也许。对用户来说是无法察觉的。通常是DOM事件的处理引入了性能惩罚,而不是事件从一个节点冒泡到另一个节点。

我发现以下事情通常会对JavaScript性能产生负面影响:

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

#1对性能影响最大。我认为,在大多数情况下,试图提高事件处理的性能是一种过早的优化。唯一需要优化事件处理代码的情况是当一个事件每秒钟触发多次(例如"scroll"和"mousemove"事件)。事件委托的另一个好处是,您不必清理将从文档树中分离的DOM节点上的事件处理程序,使浏览器可以回收那些内存。

(来自下面的评论)wvandell说:

事件委托的性能成本与实际事件“冒泡”的关系很小.......但是,在将许多选择器委托给单个父级时,会产生性能损失。

这是真的,然而我们需要考虑到被感知的性能。将许多点击事件委托给父元素不会对用户造成明显的影响,但是如果委托像scrollmousemove这样的事件,这些事件可能每秒触发超过50次(留下20毫秒来处理事件),则用户可能会察觉到性能问题。这意味着我们应该避免过早地对事件处理器代码进行优化。

许多点击事件可以在共同祖先上委托,例如document.documentElement。我应该在那里委托“mousemove”事件吗?也许吧。这取决于其他事件的情况以及委托的“mousemove”事件是否足够响应。

你说:“它代表页面上的<html>元素,其中包含了所有内容”。这并不完全正确,因为documentwindow不在<html>元素内。如果你将所有事件处理委托给document.documentElement,你会错过像popstatescrollDOMContentLoaded等事件。 - Philip Walton
@GregBurghardt 我认为你写的一些内容没有抓住问题的重点,或者歪曲了事件委托的实际情况。事件委托的性能成本与事件的“冒泡”实际上没有多大关系(正如你所正确指出的)。然而,当将许多选择器委托给单个父级时,会产生性能损失。来自jQuery .on()文档 - wvandaal
在文档树的顶部附近附加许多委托事件处理程序可能会降低性能。每次事件发生时,jQuery必须将该类型的所有附加事件的所有选择器与从事件目标到文档顶部路径上的每个元素进行比较。为了获得最佳性能,请在尽可能靠近目标元素的文档位置附加委托事件。避免在大型文档上过度使用document或document.body进行委托事件。 - wvandaal
@wvandall:所以jQuery是性能瓶颈,而不是在<html>元素上委托事件。 - Greg Burghardt
@PhilipWalton:我应该编辑我的帖子。我在使用“包含所有内容”这个术语时非常宽泛。它包含了用户可以与之交互的所有HTML标签。 - Greg Burghardt
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接