How does jQuery.on() function?

前端 未结 2 1986
北荒
北荒 2020-12-06 11:07

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-h
相关标签:
2条回答
  • 2020-12-06 11:47

    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. =]

    0 讨论(0)
  • 2020-12-06 11:56

    Necromancing:
    Just in case anybody needs to replace JQuery on/live with Vanilla-JavaScript:

    TypeScript:

    /// attach an event handler, now or in the future, 
    /// for all elements which match childselector,
    /// within the child tree of the element maching parentSelector.
    export function subscribeEvent(parentSelector: string | Element
        , eventName: string
        , childSelector: string
        , eventCallback)
    {
        if (parentSelector == null)
            throw new ReferenceError("Parameter parentSelector is NULL");
    
        if (childSelector == null)
            throw new ReferenceError("Parameter childSelector is NULL");
    
        // nodeToObserve: the node that will be observed for mutations
        let nodeToObserve: Element = <Element>parentSelector;
        if (typeof (parentSelector) === 'string')
            nodeToObserve = document.querySelector(<string>parentSelector);
    
    
        let eligibleChildren: NodeListOf<Element> = nodeToObserve.querySelectorAll(childSelector);
    
        for (let i = 0; i < eligibleChildren.length; ++i)
        {
            eligibleChildren[i].addEventListener(eventName, eventCallback, false);
        } // Next i 
    
        // https://stackoverflow.com/questions/2712136/how-do-i-make-this-loop-all-children-recursively
        function allDescendants(node: Node)
        {
            if (node == null)
                return;
    
            for (let i = 0; i < node.childNodes.length; i++)
            {
                let child = node.childNodes[i];
                allDescendants(child);
            } // Next i 
    
            // IE 11 Polyfill 
            if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
    
            if ((<Element>node).matches)
            {
                if ((<Element>node).matches(childSelector))
                {
                    // console.log("match");
                    node.addEventListener(eventName, eventCallback, false);
                } // End if ((<Element>node).matches(childSelector))
                // else console.log("no match");
    
            } // End if ((<Element>node).matches) 
            // else console.log("no matchfunction");
    
        } // End Function allDescendants 
    
    
        // Callback function to execute when mutations are observed
        let callback:MutationCallback = function (mutationsList: MutationRecord[], observer: MutationObserver)
        {
            for (let mutation of mutationsList)
            {
                // console.log("mutation.type", mutation.type);
                // console.log("mutation", mutation);
    
                if (mutation.type == 'childList')
                {
                    for (let i = 0; i < mutation.addedNodes.length; ++i)
                    {
                        let thisNode: Node = mutation.addedNodes[i];
                        allDescendants(thisNode);
                    } // Next i 
    
                } // End if (mutation.type == 'childList') 
                // else if (mutation.type == 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.');
    
            } // Next mutation 
    
        }; // End Function callback 
    
        // Options for the observer (which mutations to observe)
        let config = { attributes: false, childList: true, subtree: true };
    
        // Create an observer instance linked to the callback function
        let observer = new MutationObserver(callback);
    
        // Start observing the target node for configured mutations
        observer.observe(nodeToObserve, config);
    } // End Function subscribeEvent 
    

    JavaScript:

     /// attach an event handler, now or in the future, 
        /// for all elements which match childselector,
        /// within the child tree of the element maching parentSelector.
        function subscribeEvent(parentSelector, eventName, childSelector, eventCallback) {
            if (parentSelector == null)
                throw new ReferenceError("Parameter parentSelector is NULL");
            if (childSelector == null)
                throw new ReferenceError("Parameter childSelector is NULL");
            // nodeToObserve: the node that will be observed for mutations
            var nodeToObserve = parentSelector;
            if (typeof (parentSelector) === 'string')
                nodeToObserve = document.querySelector(parentSelector);
            var eligibleChildren = nodeToObserve.querySelectorAll(childSelector);
            for (var i = 0; i < eligibleChildren.length; ++i) {
                eligibleChildren[i].addEventListener(eventName, eventCallback, false);
            } // Next i 
            // https://stackoverflow.com/questions/2712136/how-do-i-make-this-loop-all-children-recursively
            function allDescendants(node) {
                if (node == null)
                    return;
                for (var i = 0; i < node.childNodes.length; i++) {
                    var child = node.childNodes[i];
                    allDescendants(child);
                } // Next i 
                // IE 11 Polyfill 
                if (!Element.prototype.matches)
                    Element.prototype.matches = Element.prototype.msMatchesSelector;
                if (node.matches) {
                    if (node.matches(childSelector)) {
                        // console.log("match");
                        node.addEventListener(eventName, eventCallback, false);
                    } // End if ((<Element>node).matches(childSelector))
                    // else console.log("no match");
                } // End if ((<Element>node).matches) 
                // else console.log("no matchfunction");
            } // End Function allDescendants 
            // Callback function to execute when mutations are observed
            var callback = function (mutationsList, observer) {
                for (var _i = 0, mutationsList_1 = mutationsList; _i < mutationsList_1.length; _i++) {
                    var mutation = mutationsList_1[_i];
                    // console.log("mutation.type", mutation.type);
                    // console.log("mutation", mutation);
                    if (mutation.type == 'childList') {
                        for (var i = 0; i < mutation.addedNodes.length; ++i) {
                            var thisNode = mutation.addedNodes[i];
                            allDescendants(thisNode);
                        } // Next i 
                    } // End if (mutation.type == 'childList') 
                    // else if (mutation.type == 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.');
                } // Next mutation 
            }; // End Function callback 
            // Options for the observer (which mutations to observe)
            var config = { attributes: false, childList: true, subtree: true };
            // Create an observer instance linked to the callback function
            var observer = new MutationObserver(callback);
            // Start observing the target node for configured mutations
            observer.observe(nodeToObserve, config);
        } // End Function subscribeEvent 
    
    0 讨论(0)
提交回复
热议问题