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

Most efficient way to dynamically bind event handlers

本文关键字:有效 方法 事件处理 程序 动态绑定      更新时间:2023-09-26

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

来源

  • SO:所有jQuery事件都应该绑定到文档吗
  • SO:如何找到两个或多个节点的最近共同祖先
  • jQuery文档:$.fn.on()

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

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

至于事件委派的效率,事件必须出现的节点越多,所需时间就越长,但我们谈论的是大约1到2毫秒的时间差——可能。用户无法察觉。通常是对DOM事件的处理会带来性能损失,而不是将事件从一个节点冒泡到另一个节点。

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

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

主要是,#1的影响最大。我认为在大多数情况下,试图在事件处理中获得性能提升是一种过早的优化。我看到的优化事件处理代码的唯一情况是,当您有一个每秒触发多次的事件(例如"滚动"answers"鼠标移动"事件)时。事件委派的另一个好处是,您不必清理将从文档树分离的DOM节点上的事件处理程序,从而允许浏览器垃圾收集内存。

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

事件委派的性能成本与事件的实际"冒泡"关系不大。。。将多个选择器委派给单个父级时会对性能造成影响。

这是真的,但是让我们想想感知到的性能。委派许多点击事件对用户来说不会很明显。如果委派像scrollmousemove这样的事件,每秒可以触发50次以上(剩下20毫秒来处理该事件),则用户可能会察觉到性能问题。这又回到了我反对过早优化事件处理程序代码的论点。

许多点击事件可以在一个共同的祖先(如document.documentElement)上毫无问题地进行委派。我会在那里委托一个"mousemove"事件吗也许吧这取决于其他情况,以及委托的"mousemove"事件是否足够响应。