直接绑定与委托绑定 - jQuery .on()

170

我正在尝试理解使用jQuery .on()方法时直接事件处理程序和委托事件处理程序之间的特定差异。具体来说,是这段话的最后一句:

当提供了一个选择器时,事件处理程序称为委托。当事件直接发生在绑定元素上时,处理程序不会被调用,而只会应用于与选择器匹配的后代元素。jQuery从事件目标向上传播事件到处理程序所附加的元素(即从内部到外部元素),并在该路径上匹配选择器的任何元素上运行处理程序。

"在任何元素上运行处理程序"是什么意思?我创建了一个测试页面来尝试这个概念。但是以下两种结构都导致相同的行为:

$("div#target span.green").on("click", function() {
   alert($(this).attr("class") + " is clicked");
});

或者,

$("div#target").on("click", "span.green", function() {
   alert($(this).attr("class") + " is clicked");
});

也许有人可以引用不同的例子来阐明这一点?谢谢。


7
有兴趣的人可以访问此链接:http://jsperf.com/jquery-fn-on-delegate-vs-direct。 - dgo
1
@KevinWheeler 我在下面评论了你的小工具,但是这里,基本上它没有正确设置(你绑定到父元素,而委托是针对子元素的)。回答你的问题moey,它意味着委派处理程序将匹配新添加的元素,而没有委派的处理程序则不会。委派的好处是减少了挂接到浏览器中的事件,从而降低了应用程序的内存消耗,但是代价是增加了处理单击所需的时间(最小化)。如果你正在制作游戏,请不要委派。 - vipero07
1
你提到的“测试页面”无法正常工作。 - Jaime Montoya
6个回答

394

情况1(直接):

$("div#target span.green").on("click", function() {...});

== 嘿!我想让div#target内的每个span.green注意了:当你被点击时,执行X操作。

Case 2(委托):

$("div#target").on("click", "span.green", function() {...});

== 嘿,div#target!当你的任何子元素,即 "span.green" 被点击时,对它们执行 X 操作。

换句话说...

第一种情况下,每个 span 都单独被赋予了指令。如果创建了新的 span,则它们将不会听到指令并且无法响应点击。每个 span 都 直接负责 自己的事件。

在第二种情况下,只有容器被赋予了指令;它负责代表其子元素注意到点击。捕捉事件的工作已经被 委托。这也意味着该指令将针对未来创建的子元素执行。


51
这是一个很好的解释,让我理解了一个我长期以来一直不愿意去理解的问题。谢谢! - dgo
3
为什么 on() 函数可以接受两个参数,这不是和使用 click() 差不多吗?请帮我翻译一下。 - nipponese
6
.on() 是一个通用的 API,可以处理任何类型的事件,包括多个不同的事件(你可以在第一个字符串中输入多个事件名称)。.click() 只是对第一种形式的简写。 - N3dst4
2
@newbie, @N3dst4:e.target将是单击事件的初始目标(如果span.green有子节点,则可以是子节点)。在处理程序内部,您应该使用this引用。请参见[此fiddle](http://jsfiddle.net/LeGEC/JRHWX/1/)。 - LeGEC
1
@N3dst4 - 请写一本涵盖所有类似主题的问题的书。对于正在学习的人来说,这种类比非常棒。我为这个答案鼓掌,感激不尽! - K7Buoy
显示剩余12条评论

6
N3dst4 的解释非常完美。基于此,我们可以假设所有子元素都在 body 中,因此我们只需要使用这个:
$('body').on('click', '.element', function(){
    alert('It works!')
});

它可以与直接或委托事件一起使用。


3
jQuery建议不使用body,因为它较慢,脚本必须搜索body内的所有子元素,在大多数情况下应该有很多。更好(更快)的方法是使用元素的立即父容器。 - mikesoft
2
Jquery已删除了live方法,该方法与您展示的方法相同。这种方法性能较差,不应使用。 - Maciej Sikora
在现代浏览器中,$('body').on().element 委托应该与本地的 document.body.addEventHandler() 表现完全相同,并在回调中使用 if (Event.target.className.matches(/\belement\b/))。由于 $ .proxy 开销,它可能会稍微慢一些,但不要引用我。 - cowbert

6
第一种方式,$("div#target span.green").on(),直接将点击处理程序绑定到与选择器匹配的span上,这意味着如果稍后添加其他span(或更改其类以匹配),它们将被忽略并且不会有点击处理程序。这也意味着,如果稍后从其中一个span中删除“green”类,则其单击处理程序仍将继续运行 - jQuery不会跟踪如何分配处理程序并检查选择器是否仍然匹配。
第二种方式,$("div#target").on(),将点击处理程序绑定到与之匹配的div上(同样,这是针对那些在那一刻匹配的div),但当在div中的某个位置发生单击时,只有当单击发生在与.on()的第二个参数中的选择器“span.green”匹配的子元素中时,处理程序函数才会运行。以这种方式完成,不管这些子span何时创建,单击它们都将运行处理程序。
因此,对于没有动态添加或更改其内容的页面,您不会注意到两种方法之间的区别。如果您正在动态添加额外的子元素,则第二种语法意味着您不必担心为它们分配单击处理程序,因为您已经在父级上执行了一次。

性能方面会有明显的差异吗?比如将更改监听器放置在20个元素上,还是只放在它们的父级上,并在之后使用目标获取它们。我只是好奇(我知道20是非常小的数字,也许是2000,我不知道)。 - Vitaliy Terziev
1
@VitaliyTerziev - 使用委托处理程序(选项2)在调用.on()时会更快,因为它仅选择一个元素并绑定一个处理程序。实际运行处理程序时发生事件的性能可能不会有足够的差异,以至于用户注意到差异。 - nnnnnn

3

离题一点,但是帮助我搞清楚这个功能的概念是:绑定元素必须作为选定元素的父节点

  • “Bound”指的是.on剩余的部分。
  • “Selected”指的是.on()的第二个参数。

委托不像.find()那样选择绑定元素的子集。 选择器仅适用于严格的子元素。

$("span.green").on("click", ...

与之非常不同

$("span").on("click", ".green", ...

特别注意,为了获得@N3dst4所提到的“在未来创建的元素”的优势,绑定的元素必须是永久的父元素。然后选定的子元素可以随时添加或删除。

编辑

委托 .on 失效的检查清单

$('.bound').on('event', '.selected', some_function) 可能失效的一些棘手原因:

  1. 绑定的元素不是永久的。它是在调用 .on() 之后创建的。
  2. 选定的元素不是绑定元素的合适子元素。它们是同一个元素。
  3. 选定的元素通过调用 .stopPropagation() 阻止了事件冒泡到绑定元素。

(省略一些不那么棘手的原因,例如拼写错误的选择器.)


1
我写了一篇关于直接事件和委托事件的比较文章。我比较了纯JS,但对于只封装它的jQuery来说意义相同。
结论是,委托事件处理适用于动态DOM结构,其中绑定的元素可以在用户与页面交互时创建(无需再次绑定),而直接事件处理适用于静态DOM元素,当我们知道结构不会改变时。
有关更多信息和完整比较,请参见http://maciejsikora.com/standard-events-vs-event-delegation/
始终使用委托处理程序,这是当前非常流行的做法,但并不正确,许多程序员使用它,因为“应该使用”,但事实是,直接事件处理程序对某些情况更好,并且使用哪种方法应该由差异的知识支持。

如果要附加许多事件处理程序(例如,表格的每一行),从性能角度来看,通常最好使用单个委派处理程序到容器,而不是附加许多直接处理程序。您可以从基本事实中看到这一点,即事件处理程序计数本身就是一种分析指标。 - cowbert

0

情况三(委派):

$("div#target").delegate("span.green", "click", function() {...});

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