Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the order of inline onclick vs addeventlistener and why?

Consider this button:

<button id="the_button" onclick="RunOnClick();">Click</button>

This button has an inline onclick event handler. The contents of the RunOnClick function don't matter for this question.

If I ALSO attach another click event listener like so:

var btn = document.getElementById('the_button');

btn.addEventListener("click", function(){
    // Do Something
});

The handler registered with addEventListener always seems to run after the inline onclick="" one.

Can I always rely on the single inline onclick handler to fire first and the addEventListener handler to fire later?

Is this a fluke? Or is it actually designed that way and part of one of the ECMAScript specifications?

This feels like a back to the basics quality of question, but I don't know the answer.

like image 969
Captainlonate Avatar asked Apr 12 '18 21:04

Captainlonate


1 Answers

The answer is, yes, this is actually covered in the specification but not the ECMAScript spec per se, which only strictly deals with ECMAScript independent of implementation.

Firstly, the ordering of DOM events. I refer you to this relevent SO question.

Are event handlers in JavaScript called in order?

As this states, previously it was unspecifed but as of the DOM level 3 spec it does specifically state they should be executed in the order that they are registered.

But what about inline event handlers defined like in your example?

For this we can turn to the HTML 5 spec which says:

An event handler content attribute is a content attribute for a specific event handler. The name of the content attribute is the same as the name of the event handler.

[...]

When an event handler H of an element or object T implementing the EventTarget interface is first set to a non-null value, the user agent must append an event listener to the list of event listeners associated with T with type set to the event handler event type corresponding to H and callback set to the event handler processing algorithm defined below.

Unpacking this and the subsequent notes a bit the key things to take away here are:

  • The act of adding an 'event handler content attribute' (e.g. your onclick attribute) will cause an event listener to be added to the list referred to earlier in the DOM spec.
  • What is actually happening behind the scenes is that this event listener is not strictly the code you specified in the onclick attribute but an internal event handler processing algorithm for evaluating the attribute and executing the appropriate callback.
  • Importantly when the attribute changes or is set to null then the event listener, and it's position in the list, is not changed. Only the outcome of the event handler processing algorithm is changed (because you have changed the input).

This might be a bit confusing. It might help to read and digest the notes on the spec yourself, but I will also try to cover the key implications below.

Firstly, yes, the behaviour you see is effectively defined by the spec as a logical result of what the spec tells us. When a document is being parsed and encounters an inline event handler attribute then this internal algorithm is added to the list of event listeners immediately. According to the spec then this will mean that the first event listener will be the one corresponding to your event handler attribute. Since this has to have been set before any calls to addEventListener since it wouldn't be possible to call addEventListener on a element that didn't exist then. In these circumstances it will always execute first.

The interesting stuff happens when we start messing with the inline attribute after the initial parsing. Here's an example from the HTML5 spec itself that appears right after the bit I quoted above:

EXAMPLE 8

This example demonstrates the order in which event listeners are invoked. If the button in this example is clicked by the user, the page will show four alerts, with the text "ONE", "TWO", "THREE", and "FOUR" respectively.

 <button id='test'>Start Demo</button>
    <script>
    var button = document.getElementById('test');
    button.addEventListener('click', function () { alert('ONE') }, false);
    button.setAttribute('onclick', "alert('NOT CALLED')"); // event handler listener is registered here
    button.addEventListener('click', function () { alert('THREE') }, false);
    button.onclick = function () { alert('TWO'); };
    button.addEventListener('click', function () { alert('FOUR') }, false);
    </script>

As we can see then the initial value of the onclick attribute is overriden, but the new onclick handler still executes between the first and the second listener set with addEventListener. The reason for this is that the inline event handler will effectively always be in the list of listeners at the same point it was when it was first added to the element. This is because technically, as stated earlier, the actual event listener is not the callback we have specified in the attribute content, but an internal algorithm that takes the attribute contents as it's input.

I have created a JSFiddle to test this is the case and I can confirm this is the behaviour I see in both Firefox and Chrome.


So to summarise, in practical terms:

  1. Event handler attributes, encountered in a documents source when first loading, will always execute first since they could not have had an event listener added before.
  2. For event handler attributes added later with e.g setAttribute, then they will respect the order in which they were added respective to earlier and later calls to addEventListener.
  3. However changing or unsetting the value of an event handler attribute will not change it's position in the list of event listeners.

Hope that clears things up!

like image 143
ChrisM Avatar answered Oct 31 '22 13:10

ChrisM