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

IT技术 javascript jquery jquery-events event-delegation
2021-01-24 03:01:22

我有一些这样的标记(类只是为了说明):

<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>

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

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

li.collapsed section {
  display: none;
}

(附带问题:这是否是使用 details/summary HTML5 标签的合适地方?关于这些标签是否会进入最终规范似乎有点不确定,我想要一个滑动过渡,所以看起来我会无论如何都需要 JS。但我把这个问题抛给了大众。你好,大众。)

如果根列表是页面加载时保证存在的唯一(相关)元素,为了 .on() 有效工作,我必须将所有事件绑定到该元素并为每个事件拼出精确的选择器,因为我明白它。因此,例如,要将单独的功能绑定到彼此相邻的两个按钮,我必须每次都完整地拼出选择器,à la

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

那是准确的吗?既然如此,在将新项目添加到页面时放弃 .on() 并绑定我的事件是否更有意义?如果这会对响应产生影响,则项目总数可能最多为几十个。

2个回答

您将在绑定事件时创建更少的 CPU 开销,$(<root-element>).on(<event>, <selector>)因为您将绑定到单个“根”元素而不是可能更多的单个后代元素(每个绑定都需要时间......)。

话虽如此,当实际事件发生时,您将产生更多的 CPU 开销,因为它们必须将 DOM 冒泡到“根”元素。

小故事:委托在绑定事件处理程序时节省 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在谈论触发时相对更快)

看了一下性能测试。触发对每个绑定项目的点击具有误导性,因为用户可能不会这样做。从初始加载的角度来看,Delegate 无疑是性能更好的。
2021-03-17 03:01:22
@Jasper - 如果您有一个以实际使用为模型的用例,则测试完整用例的性能会很好。我从测试的构建方式推断出的用例对我来说似乎有点不真实。我想这取决于您是在寻找不同代码块的并排比较还是试图找到最佳的用户体验。由于页面加载和控件绑定比用户的单击事件更耗时,因此我希望只测量这组条件。我希望我说的有道理。:-)
2021-03-17 03:01:22
谢谢,这很棒。我认为直接绑定在我的情况下更有意义,因为在现有项目上触发的事件比添加新项目更频繁地发生。此外,这将使我对选择器更加聪明。
2021-03-24 03:01:22
@JECarterII 我没有创建很多性能测试,但我认为这个想法是为了在单击链接时显示性能的百分比差异。单击更多链接会提供更大的数据集,应该会提供更准确的统计平均值,对吗?或者我错过了什么?我还认为.find()在两个测试中使用都很好,因为它不会因为只.find()在一个测试中使用而不在另一个测试中使用而影响性能,这就是我相信您的测试存在如此性能差距的原因。
2021-04-08 03:01:22
@jasper 您的 jsperf 测试结果无效,因为您在绑定事件后没有清理它们。这是我交换了测试顺序并将点击模拟与实际绑定分开的测试版本:jsperf.com/jquery-delegate-vs-bind-triggering/44 我认为你应该更喜欢委托大多数情况下。
2021-04-08 03:01:22

由于接受的答案有不准确的测试(顺便说一句:测试你的代码,衡量性能,不要只是盲目地遵循一些“规则” ——这不是优化的方式!)而且我发布固定测试是完全错误的: https:// jsperf.com/jquery-delegate-vs-bind-triggering/49

这证明在这样一个简单的例子中,委托或直接绑定之间没有区别

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

如果您在单个事件(例如单击)上有 1 毫秒的差异(不会发生,但这只是一个示例),您将不会注意到这一点。如果您在每秒发生 100 次的事件上有 1 毫秒的差异 - 您会注意到 CPU 消耗。

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

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