Event delegation vs direct binding when adding complex elements to a page
Asked Answered
S

2

19

I have some markup like this (classes are just for explication):

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

That is, nested sortable lists. The sortable plugin suffices, however, since each li (hereafter "item") maintains its level, though the inner lists are connected. The items have an always-visible header and a section visible when in expanded state, toggled by clicking the header. The user can add and remove items from either level at will; adding a top-level item will include an empty nest list inside it. My question is with respect to JS initialization of the newly created item: While they will share some common functionality, which I can cover via

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

and

li.collapsed section {
  display: none;
}

(Side question: would this be an appropriate place to use the details/summary HTML5 tags? It seems sort of iffy about whether those will even make it into the final spec, and I want a sliding transition, so it seems like I'd need JS for that anyway. But I throw the question to the masses. Hello, masses.)

If the root list is the only (relevant) element guaranteed to be in existence at page load, for .on() to work effectively, I have to bind all the events to that element and spell out the precise selector for each, as I understand it. So, for example, to tie separate functions to two buttons right next to each other, I'd have to spell out the selector in full each time, à la

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

Is that accurate? That being the case, does it make more sense to forgo .on() and bind my events at the time the new item is added to the page? The total number of items will probably number in the dozens at most, if that makes a difference in the response.

Squelch answered 11/1, 2012 at 22:10 Comment(5)
+1, I'd like to see a detailed response maybe outlining performance considerations.Ellersick
In my opinion either approach would be fine if you know that the upper limit is dozens at most. But I'm sure you know sometimes these things have a way of growing over time...Vinylidene
Will there be dozens of elements in total or can there be dozens of a single type of element? In your example button.b2, will there be one of these or potentially dozens?Upandcoming
@nnnnnn, this case is almost certainly bounded in the low triple digits, but there are a lot of enthusiastic copy-pasters around here, and who knows what they'll borrow the code for, so point taken.Squelch
@JamesMontagne, the outer list is collections, where the only metadata is the name, but the inner lists are assets within those collections, which have a whole bunch of metadata that needs to be displayed/manipulated--this is part of a CMS.Squelch
H
45

You will create less CPU overhead in binding the events using $(<root-element>).on(<event>, <selector>) since you will be binding to a single "root" element instead of potentially many more single descendant elements (each bind takes time...).

That being said, you will incur more CPU overhead when the actual events occur as they have to bubble up the DOM to the "root" element.

Short-story: delegate saves CPU when binding event handlers; bind saves CPU when events trigger (e.g. a user clicks something).

So it's up to you to decide what point is more important for performance. Do you have available CPU when you add the new elements? If so then binding directly to the new elements would be the best for overall performance however if adding the elements is a CPU intensive operation you will probably want to delegate the event binding and let the event triggering create some extra CPU overhead from all the bubbling.

Note that:

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

is the same as:

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

and that:

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

is the same as:

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

.on() is new in jQuery 1.7 and if you are using 1.7+ then .delegate(<selector>, <event>, <event-handler>) is just a short-cut for .on(<event>, <selector>, <event-handler>).

UPDATE

Here is a performance test showing that it is faster to delegate event binding than to bind to each element individually: http://jsperf.com/bind-vs-click/29. Sadly this performance test has been removed.

UPDATE

Here is a performance test showing that event triggering is faster when you bind directly to elements rather than delegate the binding: http://jsperf.com/jquery-delegate-vs-bind-triggering (Note that this isn't a perfect performance test since the binding methods are included in the test, but since delegate runs faster on binding it just means that bind is even faster relatively when talking about triggering)

Higher answered 11/1, 2012 at 22:19 Comment(6)
Thanks, this is great. I think direct binding makes more sense in my case, since events fired on an existing item will happen more frequently than adding a new item. Plus this will allow me to be smarter about selectors.Squelch
Took a look at the performance tests. Triggering click on every bound item is misleading as users will likely not do so. Delegate is far and away the better performer from an initial load perspective.Basinet
@JECarterII I don't create many performance tests but I thought the idea was to show the % difference in performance when clicking links. Clicking more links gives a greater data set that should give a more accurate statistical average, correct? Or am I missing something? I also thought using .find() in both tests was good because it didn't skew the performance by only using .find() in one test and not the other, which is why your test has such a performance gap I believe.Higher
@Higher - Testing performance of the full use case is good if you have a use case modeled on real use. The use case I infer from the way the test was constructed seemed a bit unreal to me is all. I guess it depends on whether you're looking for a side by side comparison of different chunks of code or trying to find the best possible user experience. As page load and control binding is more time consuming than the click event for the user, I look to measure only that set of conditions. I hope I'm making some sense. :-)Basinet
@jasper The results of your jsperf test are invalid, because you aren't cleaning up your events after binding them. Here is a version of the test where I have swapped the order of your tests and separated out the click simulation from the actual binding: jsperf.com/jquery-delegate-vs-bind-triggering/44 I think that you should prefer delegate in most cases.Phthalocyanine
I'd also like to point out, that because your first test adds a ton of overhead to the delegate results in the form of needless handlers, which is why it looks slower. Delegate is equal or faster in most cases.Phthalocyanine
D
17

Since the accepted answer has inaccurate tests (BTW: test your code, measure performance, don't just blindly follow some "rules" - this is not how optimization is done!) and is simply wrong I post fixed tests: https://jsperf.com/jquery-delegate-vs-bind-triggering/49

which proves on such simple example there is NO difference between delegation or direct binding

The only cases when delegation is always bad are events like mouse move and scroll - which are triggered x times per second. THIS is where you will notice any performance difference.

If you have even 1ms difference (won't happen, but this is just an example) on single event - like click - you won't notice that. If you have 1ms difference on event that happens 100 times in a second - you will notice CPU consumption.

Just having thousand of elements won't negatively impact your performance with delegation - actually - this is the case when they should be used - to avoid hogging CPU when attaching thousand of event handlers.

So if you really need a rule to follow (don't do that) - use delegation on everything except mouse move, scroll and other events that you can expect to fire continuously.

Doble answered 25/12, 2015 at 16:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.