javascriptjquerydom-eventsevent-delegationjquery-on

How does jQuery.on() function?


I have not seen the source of this function, but I wonder, does it work like this:

  1. Selects element(s) by their selectors
  2. Delegates the provided event-handlers to them
  3. Runs a setInterval on that selector and constantly un-delegating, and then re-delegating that same event all over

Or there is a pure-JavaScript DOM explanation to this?


Solution

  • I assume your question is about the Event Delegation version of .on.

    In JavaScript, most events bubble up in the DOM hierarchy. That means, when an event that can bubble fires for an element, it will bubble up to the DOM until document level.

    Consider this basic markup:

    <div>
       <span>1</span>
       <span>2</span>
    </div>
    

    Now we apply event delegation:

    $('div').on('click', 'span', fn);
    

    The event handler is attached solely to the div element. As the span are inside of the div, any click in the spans will bubble up to the div, firing the div's click handler. At that moment, all that is left is checking whether the event.target (or any of the elements between the target and the delegateTarget) matches the delegation target selector.


    Let's make the a bit more complex:

    <div id="parent">
        <span>1</span>
        <span>2 <b>another nested element inside span</b></span>
        <p>paragraph won't fire delegated handler</p>
    </div>
    

    The basic logic is as follows:

    //attach handler to an ancestor
    document.getElementById('parent').addEventListener('click', function(e) {
        //filter out the event target
        if (e.target.tagName.toLowerCase() === 'span') {
            console.log('span inside parent clicked');
        }
    });
    

    Though the above will not match when event.target is nested inside your filter. We need some iteration logic:

    document.getElementById('parent').addEventListener('click', function(e) {
        var failsFilter = true,
            el = e.target;
        while (el !== this && (failsFilter = el.tagName.toLowerCase() !== 'span') && (el = el.parentNode));
        if (!failsFilter) {
            console.log('span inside parent clicked');
        }
    });
    

    Fiddle

    edit: Updated code to match only descendant elements as jQuery's .on does.

    Note: These snippets are for explanatory purposes, not be used in real world. Unless you don't plan to support old IE, of course. For old IE you would need to feature test against addEventListener/attachEvent as well as event.target || event.srcElement, and possibly some other quirks such as checking whether the event object is passed to handler function or available in the global scope. Thankfully jQuery does all that seamlessly behind the scenes for us. =]