事件委托与直接绑定:在向页面添加复杂元素时的选择。

19
我有一些像这样的标记(类只是为了说明):
<ol id="root" class="sortable">
  <li>
    <header class="show-after-collapse">Top-Line Info</header>
    <section class="hide-after-collapse">
      <ol class="sortable-connected">
        <li>
          <header class="show-after-collapse">Top-Line Info</header>
          <section class="hide-after-collapse">
            <div>Content A</div>
          </section>
        </li>
      </ol>
    </section>
  </li>
  <li>
    <header/>
    <section class="hide-after-collapse">
      <ol class="sortable-connected">
        <li>
          <header/>
          <section class="hide-after-collapse">
            <div>Content B</div>
          </section>
        </li>
      </ol>
    </section>
  </li>
</ol>

也就是说,嵌套可排序列表。Sortable插件就足够了,因为每个li元素(以下称为“项目”)都保持其级别,尽管内部列表是连通的。项目具有始终可见的标题以及在展开状态下可见的部分,可以通过点击标题进行切换。用户可以随意添加和删除任何级别的项目;添加一个顶层项目将在其中包含一个空的嵌套列表。我的问题与JS初始化新创建的项目有关:虽然它们将共享一些公共功能,但我可以通过使用

$("#root").on("click", "li > header", function() {
  $(this).parent().toggleClass("collapsed");
});

并且

li.collapsed section {
  display: none;
}

(附带问题:这是否是使用details/summary HTML5标签的适当位置?似乎不确定那些标签是否会被纳入最终规范中,而且我想要一个滑动转换,所以似乎我仍需要JS。但我向大众提出问题。你好,大众。)

如果根列表是在页面加载时唯一存在的(相关)元素,为了让.on()有效地工作,我必须将所有事件都绑定到该元素,并为每个事件明确指定选择器,据我所知。所以,例如,要将单独的函数绑定到两个紧挨着的按钮上,我必须每次都完整地拼写出选择器,像这样:

$("#root").on("change", "li > section button.b1", function() {
  b1Function();
}).on("change", "li > section button.b2", function() {
  b2Function();
});

这是否准确?如果是这样,那么放弃使用 .on() 并在新项目添加到页面时绑定事件更有意义吗?总项目数最多可能只有几十个,如果这会影响响应,请告诉我。


+1,我希望能看到一份详细的回复,可能概述性能方面的考虑。 - jondavidjohn
在我看来,如果你“知道”上限最多只有几十个,那么任何一种方法都可以。但我相信你知道有时这些事情会随着时间的推移而增长... - nnnnnn
总共会有几十个元素,还是可能只有几十个相同类型的元素?在你的例子button.b2中,这种元素只有一个还是可能有几十个? - James Montagne
@nnnnnn,这个情况几乎肯定在低三位数范围内,但是这里有很多热情的复制粘贴者,谁知道他们会借用代码做什么,所以这一点需要注意。 - slk
@JamesMontagne,外部列表是集合,其中唯一的元数据是名称,但内部列表是这些集合中的资产,它们有许多需要显示/操作的元数据--这是CMS的一部分。 - slk
2个回答

45
使用$(<root-element>).on(<event>, <selector>) 绑定事件将会减少CPU开销,因为你只需要绑定到一个“root”元素上,而不是多个单独的后代元素(每次绑定都需要时间...)。
尽管如此,当事件实际发生时,它们必须冒泡到“root”元素,因此将会产生更多的CPU开销。
简而言之:当绑定事件处理程序时,使用委托可以节省CPU;当触发事件时(例如用户点击某些东西),直接绑定可以节省CPU。
因此,你需要考虑哪个点对性能更重要。当你添加新元素时,你是否有足够的CPU?如果有,则直接绑定到新元素将是整体性能最好的选择;但如果添加元素是一个消耗CPU的操作,你可能需要委托事件绑定,并让事件触发从所有冒泡中创建一些额外的CPU开销。
请注意:
$(<root-element>).on(<event>, <selector>, <event-handler>)

等同于:

$(<root-element>).delegate(<selector>, <event>, <event-handler>)

并且:

$(<selector>).on(<event>, <event-handler>)

等同于:

$(<selector>).bind(<event>, <event-handler>)

.on()是jQuery 1.7中新增的方法,如果您正在使用1.7+版本,那么.delegate(<selector>, <event>, <event-handler>)只是.on(<event>, <selector>, <event-handler>)的一种快捷方式。

更新:

这里有一个性能测试,显示委托事件绑定比分别绑定每个元素要快:http://jsperf.com/bind-vs-click/29。(遗憾的是,此性能测试已被删除)

更新:

这里有一个性能测试,显示直接绑定元素比委托绑定更快:http://jsperf.com/jquery-delegate-vs-bind-triggering。(请注意,这不是一个完美的性能测试,因为绑定方法包含在测试中,但既然delegate在绑定时运行得更快,那么就意味着当涉及触发时,bind相对来说甚至更快)。


谢谢,这很棒。我认为在我的情况下直接绑定更有意义,因为现有项目上触发的事件比添加新项目更频繁。此外,这将使我能够更加聪明地选择选择器。 - slk
看了一下性能测试。 触发每个绑定项的点击是误导性的,因为用户很可能不会这样做。 从初始加载的角度来看,委托远远优于其他表现者。 - J E Carter II
@JECarterII 我不常创建性能测试,但我认为这个想法是展示在点击链接时性能的%差异。点击更多的链接可以给出更大的数据集,应该会给出更准确的统计平均值,对吗?或者我漏掉了什么?我也认为在两个测试中都使用.find()很好,因为它不会通过仅在一个测试中使用.find()而不是另一个测试来扭曲性能,这就是为什么你的测试有如此大的性能差距的原因,我想。 - Jasper
1
@Jasper - 如果您有一个基于真实用例建模的用例,则测试完整用例的性能很好。从测试构建方式推断出的用例对我来说似乎有点不真实。我想这取决于您是要进行不同代码块的并排比较还是要找到最佳用户体验。由于页面加载和控件绑定比用户单击事件更耗时,因此我只希望测量那组条件。希望我的话有些道理。 :-) - J E Carter II
@jasper,你的jsperf测试结果无效,因为你没有在绑定事件后清理它们。这是一个版本的测试,我已经交换了你的测试顺序,并将单击模拟与实际绑定分开:http://jsperf.com/jquery-delegate-vs-bind-triggering/44我认为在大多数情况下,你应该优先选择delegate。 - jacobangel
我还想指出的是,因为你的第一个测试在委托结果中添加了大量不必要的处理程序,所以它看起来比较慢。在大多数情况下,委托是相等或更快的。 - jacobangel

17

由于接受答案的测试不准确(顺便说一下:测试你的代码,测量性能,不要盲目遵循某些“规则” - 这不是优化的方法!)并且是错误的,我发布了已修复测试: https://jsperf.com/jquery-delegate-vs-bind-triggering/49

这个简单的例子证明委托或直接绑定之间没有区别。

唯一情况下委托总是不好的是像鼠标移动和滚动这样的事件 - 它们每秒触发x次。这是你会注意到任何性能差异的地方。

如果你有1ms的差异(不会发生,但这只是一个例子)在单个事件上 - 如点击 - 你不会注意到它。如果你有1ms的差异在一秒钟内发生100次的事件上 - 你会注意到CPU的消耗。

仅仅拥有成千上万的元素不会对使用委托产生负面影响 - 实际上 - 这就是应该使用委托的情况 - 避免在附加数千个事件处理程序时占用CPU。

所以如果你真的需要遵循一个规则(不要这样做) - 在除了鼠标移动、滚动和其他你可以预期会连续触发的事件之外,对所有事情使用委托。


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