170

I am trying to understand this particular difference between the direct and delegated event handlers using the jQuery .on() method. Specifically, the last sentence in this paragraph:

When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector.

What does it mean by "runs the handler for any elements"? I made a test page to experiment with the concept. But both following constructs lead to the same behavior:

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

or,

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

Maybe someone could refer to a different example to clarify this point? Thanks.

RPichioli
  • 3,245
  • 2
  • 25
  • 29
moey
  • 10,587
  • 25
  • 68
  • 112
  • 7
    For all interested: http://jsperf.com/jquery-fn-on-delegate-vs-direct – dgo Apr 07 '15 at 16:25
  • 1
    @KevinWheeler I commented on your fiddle below but [here](https://jsfiddle.net/dohmdy2n/), essentially it isn't set up correctly (you are binding to the parent element, and delegation is intended for the children). To answer your question moey it means that the delegated handler will match newly added elements, where the one without delegation won't. Delegation has the benefit of there being less events hooked into the browser causing lower memory consumption for the app, however the tradeoff is it increases the time to process a click (minimally). If you are making a game don't delegate. – vipero07 Jun 19 '15 at 21:47
  • 1
    The "test page" you reference is not working. – Jaime Montoya Sep 11 '17 at 21:52

6 Answers6

394

Case 1 (direct):

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

== Hey! I want every span.green inside div#target to listen up: when you get clicked on, do X.

Case 2 (delegated):

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

== Hey, div#target! When any of your child elements which are "span.green" get clicked, do X with them.

In other words...

In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each span is directly responsible for its own events.

In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated. This also means that the instruction will be carried out for child elements that are created in future.

N3dst4
  • 6,360
  • 2
  • 20
  • 34
  • 51
    That is a great explanation, and has brought clarity to an issue that I have long refused to understand. Thanks! – dgo Apr 13 '13 at 13:39
  • 3
    So why does `on()` allow two arguments when that would pretty much be the same as using `click()`? – nipponese Jul 31 '13 at 20:33
  • 6
    .on() is a general purpose API that can handle any kind of event, including multiple different events (you can put multiple event names in that first string.) .click() is just a shorthand for that first form. – N3dst4 Aug 01 '13 at 07:45
  • Also, to add to the idea of delegation. Say in the future, you have more elements called "span.green" added to #target, those span.green will also fire that function. – CharlieM Oct 07 '13 at 20:20
  • @N3dst4 I have a question about this. How would I know which `span.green` is click in this way? – newbie Nov 08 '13 at 06:21
  • @newbie - the callback can take an event argument, and the event has a .target: $("div#target").on("click", "span.green", function(event) { // do something with event.target }); – N3dst4 Nov 08 '13 at 10:50
  • @N3dst4 I see, One more question. How about if there is a plenty of `span.green` then one is clicked. How can I do some like this condition: `if( not the clicked element ){ // do something }` note: they all have same class. – newbie Nov 08 '13 at 11:52
  • @newbie Can you rephrase the question? – N3dst4 Nov 08 '13 at 12:11
  • @N3dst4 sorry if its confusing. Considering this `$("div#target").on("click", "span.green", function() {...});`, Let us say that `span.green` is referencing on **3 span elements** on `div#target` and the one that has been clicked is the second element. How can I make the other span **excluding** the span (2nd span) that has been clicked to do something. – newbie Nov 08 '13 at 12:33
  • Oh I see. That's almost a separate question, but you could do this: `$("div#target span.green").not(event.target).whatever()` – N3dst4 Nov 08 '13 at 13:06
  • @N3dst4 I see, so there is a `.not()` function. Thank you for you help. – newbie Nov 08 '13 at 13:20
  • 2
    @newbie, @N3dst4 : `e.target` will be the initial target of the click event (can be a child node if `span.green` has children). From inside the handler, you should use the `this` reference. See [this fiddle](http://jsfiddle.net/LeGEC/JRHWX/1/). – LeGEC Feb 14 '14 at 09:45
  • 1
    Another note to add to the topic of delegation - it's both very useful and very efficient. You'll use up fewer browser resources with delegation vs attaching an event to every matching element. Thought I'd mention it, just in case people needed more reasons to use delegation. – phatskat Mar 13 '14 at 14:03
  • what about performances? Is the parent DOM depth-first searched every time it is clicked? – Julio Guerra Sep 30 '14 at 05:59
  • jQuery uses "event bubbling" (see http://www.quirksmode.org/js/events_order.html for details), so handlers are actually triggered child-first. So in a sense, delegated handlers cause more work because the search must go all he way up the tree to find the handler. However: that's actually a pretty quick operation, even with a deep DOM, and the savings you get in terms to not having to create and bind new handlers when your DOM changes outweight the difference. – N3dst4 Sep 30 '14 at 09:44
  • 1
    @N3dst4 - please right a book answering questions across all topics like this. For those learning, this kind of analogy is fantastic. I applaud this answer and can not thank you enough! – K7Buoy Feb 17 '17 at 09:30
  • @nipponese If you read the following syntax at http://api.jquery.com/on/ and the meaning of each parameter, you will find an answer to your question: .on( events [, selector ] [, data ], handler ). As you can see, the first parameter is "events" and the official explanation says that it is of type String and it adds this definition: 'One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".' As N3dst4 said in his comment, ".on() is a general purpose API that can handle any kind of event." The first parameter could be for example: "click submit". – Jaime Montoya Sep 11 '17 at 22:04
6

The explanation of N3dst4 is perfect. Based on this, we can assume that all child elements are inside body, therefore we need use only this:

$('body').on('click', '.element', function(){
    alert('It works!')
});

It works with direct or delegate event.

Brynner Ferreira
  • 1,527
  • 1
  • 21
  • 21
  • 3
    jquery advises against using body, as it is slower, because the script would have to search for all childs inside body, which should be a lot in most cases. It's better (faster) to use the inmediate parent container of the element. – mikesoft Apr 28 '15 at 21:19
  • 2
    Jquery removed live method which was doing the same as You showed. This is performance weak and should not be used. – Maciej Sikora Sep 06 '16 at 12:58
  • In modern browsers, `$('body').on()` delegation from `.element` should behave exactly the same as native `document.body.addEventHandler()` with an `if (Event.target.className.matches(/\belement\b/))` in the callback. It may be very slightly slower in jquery due to `$.proxy` overhead but don't quote me on that. – cowbert Jan 12 '18 at 20:43
6

The first way, $("div#target span.green").on(), binds a click handler directly to the span(s) that match the selector at the moment that code is executed. This means if other spans are added later (or have their class changed to match) they have missed out and will not have a click handler. It also means if you later remove the "green" class from one of the spans its click handler will continue to run - jQuery doesn't keep track of how the handler was assigned and check to see if the selector still matches.

The second way, $("div#target").on(), binds a click handler to the div(s) that match (again, this is against those that match at that moment), but when a click occurs somewhere in the div the handler function will only be run if the click occurred not just in the div but in a child element matching the selector in the second parameter to .on(), "span.green". Done this way it doesn't matter when those child spans were created, clicking upon them will still run the handler.

So for a page that isn't dynamically adding or changing its contents you won't notice a difference between the two methods. If you are dynamically adding extra child elements the second syntax means you don't have to worry about assigning click handlers to them because you've already done it once on the parent.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • would there be any noticeable difference performance wise, say put change listener to 20 elements or just on their parent and get ti with target after, just wondering (I know 20 is crazy small number, maybe 2000 idk – Vitaliy Terziev Jan 14 '23 at 16:05
  • 1
    @VitaliyTerziev - Using a delegated handler (option 2) will be faster at the time you call `.on()`, because it only selects one element and binds one handler. Performance actually running the handler at the time the event occurs is not likely to be different enough for the user to notice the difference. – nnnnnn Jan 16 '23 at 02:44
3

Tangential to the OP, but the concept that helped me unravel confusion with this feature is that the bound elements must be parents of the selected elements.

  • Bound refers to what is left of the .on.
  • Selected refers to the 2nd argument of .on().

Delegation does not work like .find(), selecting a subset of the bound elements. The selector only applies to strict child elements.

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

is very different from

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

In particular, to gain the advantages @N3dst4 hints at with "elements that are created in future" the bound element must be a permanent parent. Then the selected children can come and go.

EDIT

Checklist of why delegated .on doesn't work

Tricky reasons why $('.bound').on('event', '.selected', some_function) may not work:

  1. Bound element is not permanent. It was created after calling .on()
  2. Selected element is not a proper child of a bound element. It's the same element.
  3. Selected element prevented bubbling of an event to the bound element by calling .stopPropagation().

(Omitting less tricky reasons, such as a misspelled selector.)

Bob Stein
  • 16,271
  • 10
  • 88
  • 101
1

I wro te a post with a comparison of direct events and delegated. I compare pure js but it has the same meaning for jquery which only encapsulate it.

Conclusion is that delegated event handling is for dynamic DOM structure where binded elements can be created while user interact with page ( no need again bindings ), and direct event handling is for static DOM elements, when we know that structure will not change.

For more information and full comparison - http://maciejsikora.com/standard-events-vs-event-delegation/

Using always delegated handlers, which I see is current very trendy is not right way, many programmers use it because "it should be used", but truth is that direct event handlers are better for some situation and the choice which method use should be supported by knowledge of differences.

Maciej Sikora
  • 19,374
  • 4
  • 49
  • 50
  • if attaching many event handlers (for example, each row of a table), it is often better performance wise to use single delegated handler to container instead of attaching many direct handlers. You can see this from the basic fact that event handler count is by itself a profiling metric. – cowbert Jan 12 '18 at 20:45
0

Case 3 (delegated):

$("div#target").delegate("span.green", "click", function() {...});
Leo
  • 398
  • 6
  • 11