I am having a problem understanding the different behavior of $("button").click() and $("button")[0].click()

Viewed 978

While I am trying to learn jquery I learned that $(selector) returns an object which has all the match of that selector and is iterable like arrays. eg $("button") will return an object that will have access to all the button tag of DOM in such way that to access the first button tag you can use $["button"][0] for second you can use $["button"][1] and so on.

So here below is code focus on commented line 1 and line 2.

   <body>
        <button>Click me</button>
        <script>
            $(document).ready(function() {
// line 1
               $("button").click(function() {
                    console.log("1");
// line 2
                    $("button").click(); 
                });
            });
        </script>
    </body>

line 2 inside line1 event handler function is set up an infinite loop as you can see that when I Click on "Click me" button it will trigger line1 inside of which is line 2 which too will again trigger line 1 and so on. now see the below code snippet with changed line2.

        <script>
            $(document).ready(function() {
// line 1
               $("button").click(function() {
                    console.log("1");
// line 2
                    $("button")[0].click();
                });
            });
        </script>

This time it is not setting up an infinite loop but prints to console "1" only two times why so?

4 Answers

It is most likely the way in which the synthetic event is being invoked by JQuery.

Here is the JQuery snippet for event dispatch.

dispatch: function( nativeEvent ) {

        // Make a writable jQuery.Event from the native event object
        var event = jQuery.event.fix( nativeEvent );

        var i, j, ret, matched, handleObj, handlerQueue,
            args = new Array( arguments.length ),
            handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
            special = jQuery.event.special[ event.type ] || {};

        // Use the fix-ed jQuery.Event rather than the (read-only) native event
        args[ 0 ] = event;

        for ( i = 1; i < arguments.length; i++ ) {
            args[ i ] = arguments[ i ];
        }

        event.delegateTarget = this;

        // Call the preDispatch hook for the mapped type, and let it bail if desired
        if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
            return;
        }

        // Determine handlers
        handlerQueue = jQuery.event.handlers.call( this, event, handlers );

        // Run delegates first; they may want to stop propagation beneath us
        i = 0;
        while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
            event.currentTarget = matched.elem;

            j = 0;
            while ( ( handleObj = matched.handlers[ j++ ] ) &&
                !event.isImmediatePropagationStopped() ) {

                // If the event is namespaced, then each handler is only invoked if it is
                // specially universal or its namespaces are a superset of the event's.
                if ( !event.rnamespace || handleObj.namespace === false ||
                    event.rnamespace.test( handleObj.namespace ) ) {

                    event.handleObj = handleObj;
                    event.data = handleObj.data;

                    ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
                        handleObj.handler ).apply( matched.elem, args );

                    if ( ret !== undefined ) {
                        if ( ( event.result = ret ) === false ) {
                            event.preventDefault();
                            event.stopPropagation();
                        }
                    }
                }
            }
        }

        // Call the postDispatch hook for the mapped type
        if ( special.postDispatch ) {
            special.postDispatch.call( this, event );
        }

        return event.result;
    }

Reading the spec for synthetic and authentic click events:

When a user agent is to run post-click activation steps on an element, it must run the activation behavior defined for that element, if any. Activation behaviors can refer to the click event that was fired by the steps above leading up to this point.

As JQuery has implemented a custom dispatch handler for emulating click events it looks like they have added the following in order to emulate that aforementioned native html event lifecycle.

if ( special.postDispatch ) {
   special.postDispatch.call( this, event );
}

This call here is likely the suspect, proven when you look at the console output for the following. Note that you never see the console.log Done because the dispatch handler is never actually returning the event to complete it's native lifecycle (endless recursion), which I suspect is just frankly handled better by the native html implementation.

Infinite loop warning

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<body>
        <button>Click me</button>
        <script>
            $(document).ready(function() {
// line 1
               $("button").click(function() {
                    console.log("start");
// line 2
                    $("button").click(); 
                    console.log("Done");
                });
            });
        </script>
    </body>

Good find!

When you do $("button")[0] you are getting back the underlying native HTMLButtonElement object that is not a jQuery object.

$('button').click(fn) does things like below

    $("button").each(function() {
        //this === native button htmlElement.
        if (!this._event) this._event = {};
        if (!this._event.click) this._event.click = [];
        this._event.click.push(fn);
        if (!this.clickHandler) {
            this.clickHandler = e => {
                this._event.click.forEach(f => f.bind(this)(e));
            };
            this.addEventListener("click", this.clickHandler);
        }
    });

$('button').click() means

    $("button").each(function() {
        //this === native button htmlElement.
        if(this.clickHandler)this.clickHandler();
        if(typeof this.onclick === 'function') this.onclick();
    });

It's just an example, the jquery event source code is much more complex.

And why $("button")[0].click() prints '1' only twice? native click simulates a mouse click on the button element,so I assume that the loop is blocked by Explorer for a security reason.

bind events source code

trigger source code

$(“button”) returns a jq object,it calls methods on the jq object and $(“button”)[0] returns a DOM object,it calls the dom native method

Related