Javascript 动态绑定事件处理程序的最有效方法
问题:我需要在动态运行时将任意数量的事件处理程序绑定到任意数量的元素(DOM节点、Javascript 动态绑定事件处理程序的最有效方法,javascript,jquery,google-analytics,dom-events,event-delegation,Javascript,Jquery,Google Analytics,Dom Events,Event Delegation,问题:我需要在动态运行时将任意数量的事件处理程序绑定到任意数量的元素(DOM节点、窗口、文档),并且我需要能够在页面的生存期内更新动态创建(或销毁)节点的事件绑定。我可以看到解决这个问题的三种选择: 一) 窗口上的事件委派 二) 每个节点上的直接事件绑定 三) 公共祖先上的事件委派(在运行时之前是未知的,在更改DOM时可能需要重新计算) 最有效的方法是什么 一点背景 我正在处理一组需要对用户事件(单击、滚动等)进行分析跟踪的页面,我希望能够轻松地跨一组页面配置这些事件处理程序,而无需编写脚本来处
窗口、文档
),并且我需要能够在页面的生存期内更新动态创建(或销毁)节点的事件绑定。我可以看到解决这个问题的三种选择:
一) 窗口上的事件委派
二) 每个节点上的直接事件绑定
三) 公共祖先上的事件委派(在运行时之前是未知的,在更改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.documentElement
对象上委托事件,因为:
它表示页面上的
元素,该元素包含用户可以与之交互的所有HTML标记
它在JavaScript开始执行时就可以使用,不需要窗口加载或DOM就绪事件处理程序
您仍然可以捕获“滚动”事件
至于事件委派的效率,事件必须冒出的节点越多,花费的时间就越长,但是我们说的是大约1到2毫秒的时差——可能是这样。这是用户无法察觉的。通常是DOM事件的处理会导致性能损失,而不是事件从一个节点冒泡到另一个节点
我发现以下因素通常会对JavaScript性能产生负面影响:
文档树中的节点越多,浏览器对其进行操作的时间就越长
页面上事件处理程序的数量越多,JavaScript的速度就越慢,尽管您需要100个处理程序才能真正看到差异
主要是#1的影响最大。我认为,在大多数情况下,试图在事件处理中寻求性能提升是一种过早的优化。我看到的优化事件处理代码的唯一情况是,当您有一个每秒触发多次的事件时(例如“scroll”和“mousemove”事件)。事件委派的额外好处是,您不必清理DOM节点上的事件处理程序,这些事件处理程序将与文档树分离,从而允许浏览器对该内存进行垃圾收集
(从下面的评论中)wvandell说:
事件委派的性能成本与事件的实际“冒泡”关系不大。。。将多个选择器委派给单亲时会导致性能下降
这是事实,但是让我们考虑一下感知性能。委派许多点击事件不会引起用户的注意。如果您委派一个事件,如滚动
或鼠标移动
,它每秒可以触发50次以上(留出20毫秒处理事件),则用户可以察觉到性能问题。这又回到了我反对过早优化事件处理程序代码的论点
许多单击事件可以在一个共同的祖先上委派,例如document.documentElement
。我会在那里委派一个“mousemove”活动吗?大概这取决于正在发生的其他事情,以及委托的“mousemove”活动是否感觉足够灵敏。我认为这取决于。如果您想处理大多数到达文档的事件,您可以委托给它。但是如果你只处理一小部分,特别是如果事件触发很多(例如,mousemove
,滚动
),最好委托给更近的祖先。如果有很多元素,我不会绑定到每个元素。Backbone.js有一种非常优雅的方式。看,你说:“它代表页面上的元素,包含所有内容”。这并不完全正确,因为文档
和窗口
不在
元素内。如果您将所有事件处理委托给document.documentElement
,您将错过诸如popstate
、scroll
、DOMContentLoaded
等事件。@GregBurghardt我认为您所写的一些内容没有抓住问题的重点,并且/或者歪曲了事件委托的现实。事件委派的性能成本与事件的实际“冒泡”关系不大(正如您正确指出的)。然而,当将多个选择器委派给单亲时,会导致性能下降。从jQuery:在文档树顶部附近附加许多委托事件处理程序可能会降低性能。每次事件发生时,jQuery必须将该类型的所有附加事件的所有选择器与从事件目标到文档顶部的路径中的每个元素进行比较。为了获得最佳性能,请将委派事件附加到尽可能靠近目标元素的文档位置。避免在大型文档上过度使用document或document.body进行委派事件。@wvandall:因此jQuery是性能瓶颈,而不是委派
元素上的事件。@PhilipWalton:我应该编辑我的帖子。我使用的术语“保持一切”非常宽松