简体   繁体   中英

jQuery .on() delegation with selector that matches nested elements

 // Example A $("#delegate").on("click", function(event) { // executes on click on any descendant of #delegate or self (not a delegate at all) // 'this' is #delegate }); // Example B $("#delegate").on("click", "#outer", function(event) { // executes on click on any descendant of #outer or self // 'this' is #outer or #inner (depends on the actual click) }); $("#delegate").on("click", "#inner", function(event) { // executes on click on any descendant of #inner or self (nothing happens when clicking #outer) // 'this' is #inner }); // Example C (now it's getting weird) $("#delegate").on("click", "div", function(event) { // executes twice, when clicking #inner, because the event passes #outer when bubbling up // one time 'this' is #inner, and the other time 'this' is #outer // stopPropagation() // actually prevents the second execution });
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div id="delegate"> <div id="outer"> outer <div id="inner"> inner </div> </div> </div>

How do you explain this behaviour logically?

There is exactly one click event, which starts on #inner , bubbles through #outer and finally reaches #delegate .

The event is catched (exactly) once by the #delegate 's on-handler. The handler checks if event's history contains any div elements.

If this applies, the callback function should be called once. That's what I would expect. "Single Event, single Handler, single Condition, single Callback".

It gets more crazy if you take a look at the stopPropagation() behaviour. You can actually avoid the second execution, though the event has already reached #delegate . stopPropagation( should not work here.

What kind of "magic" is done in the implementation of the on-delegation logic? Do event-bubbling and program flow split up in any way?

Please don't post "practical advice" in the first place ("Use xyz instead!"). I'd like to understand why the code works the ways it does.

As you have bound events on all the divs in two ways:

  1. Using id attribute of the element div.
  2. Using the tag name of the element div.

so if you event.stopPropagation(); on the last one still the alert will come two times because you have cliked the div and also the #inner (for instance.)

Check the snippet below.

 $("#delegate").on("click", function(event) { alert(this.id); }); /* $("#delegate").on('click', "#outer", function(event) { alert(this.id); }); $("#delegate").on('click', "#inner", function(event) { alert(this.id); }); */ // now it's getting weird $("#delegate").on("click", "div", function(event) { event.stopPropagation(); alert(this.id); });
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <div id="delegate"> <div id="outer"> outer <div id="inner"> inner </div> </div> </div>

The way event delegation works is that jQuery walks up the DOM tree from the innermost target to the element that the delegation was bound to, testing each element to see if it matches the selector. If it does, it executes the handler with this bound to that element.

When event.stopPropagation is called, it sets a flag in the event object. The loop that walks the DOM tree also calls event.isPropagationStopped() . If propagation is stopped, it breaks out of the loop.

In other words, jQuery is doing its own bubbling and propagation stopping when it implements delegation, it's not making use of the browser's bubbling (except that this bubbling is necessary for the initial event to be triggered on #delegate , so that the jQuery loop will run).

Everything works as expected. Have a look at this fiddle :

$("#delegate").on("click", "div", function(event) {
  // event.stopPropagation();
  alert('div: ' + $(this).attr('id'));
});

Clicking #inner will fire the above event. The event will bubble up to #outer since #inner is a descendent of #outer . Since #outer also is a <div , the event will be fired on #outer , also. Logically, clicking on #inner will first alert "div: inner" and then "div: outer".

Calling event.stopPropagation() tells the event not to bubble up, so #outer stays uninvoked.

Other than in this fiddle :

<div id="delegate">
  <div id="first">
    first
  </div>
  <div id="second">
    second
      <div id="third">
        third, inside second
      </div>
    </div>
</div>

Clicking on third will first alert third , then second and then stop, because #first is not a parent but a sibling.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM