How does the way in which a JavaScript event handler is assigned affect its execution?

Inline listeners don't have any benefits, on the contrary, it's a very flawed way to add event listeners to HTML elements.

Differences in the execution

#1 this value inside the attribute code is bound to the element with JavaScript with. In the inline code called function (or any global variable) is first searched from the element. If it's not found (which usually is the case), an inline listener searches the function from the prototype chain of the element. If a matching property name is not found, the search reaches window, and runs the global function which was called. But if the function name conflicts with any property name on the lookup path, an error is fired, or an unexpected function is executed.

An example of how an inline listener finds action property of the wrapping form, just click the input:

function action () {
  console.log('Not a function!?');
}
<form action="" method="post">
  <input onclick="console.log(action); action();">
</form>

#2 The return value of the attribute code is actually used in particular events (like onsubmit). Returning false prevents the default action of the event. The return value from a listener attached with addEventListener is always fully ignored (there's no receiver for the value).

#3 All the variables and functions used in the handler must be globally accessible. This could be counted as a flaw too.

Often misunderstood behavior

A function called in the attribute code is not the actual event handler function, the code in the attribute itself is the attached handler. Hence the event objet and correct this value are present in the attribute handler code only. If you're going to need either of these values in a global function, you've to pass them manually when calling the global function.

This is normal behavior of any JavaScript code, also in an event handler attached with addEventListener. If you'll call another function from an event handler, you've to pass the event object, and bind/pass this value, if that other function needs these values.

An example of calling another function from event listeners.

function show () {
  console.log('Called:', typeof event, this.toString());
}

const inp = document.querySelectorAll('input')[1];
inp.addEventListener('click', function (e) {
  console.log('addEventListener:', typeof e, this.toString());
  show();
});
<input onclick="console.log('Inline:', typeof event, this.toString()); show();" placeholder="Inline listener">
<input placeholder="addEventListener">

As we can see, there's no difference – between attaching types – how event object and this value are handled. When an event fires, in an inline listener the code in the attribute is the code first to execute, whereas with other attaching types, the first executed code is the handler function itself. (The event object part in the example is partially irrelevant, since almost all browsers are currently implementing the global event object.)

Flaws in inline listeners

#1 You can attach only a single listener of the same type to an element.

#2 Inline listeners are potential attacking vectors, as both, the listener code in the attribute, and any function called from the attribute code are easy to override with DevTools.

#3 When writing an inline listener, quoting of the strings correctly is complex. Complexity of the quoting increases when writing a dynamic tag on the server, since you've to take care of the HTML quoting, JS quoting and the server-side language quoting.

#4 Inline listeners are not working in modules and browser extensions (these environments are not in the global namespace, and you can't call functions written within a module or plug-in code from an inline listener), and are not accepted by many frameworks, and they won't pass any security audits.

#5 Inline listeners break Separation of concerns principle by mixing presentation and action layers of the page.

element.onxxxx

onxxxx property doesn't suffer from the flaws #3, #4 and #5, but you can't add more than a single listener with an onxxxx property, and since elements are global anyway, the listener is easy to override with DevTools.

addEventListener

As a conclusion: Use addEventListener to attach events to HTML elements, it has no flaws. this is correctly bound, event object is correctly passed, multiple same type of the events can be attached, and without security risks (when the handler is not a globally accessible function), because everything happens in encapsulated code, without needing a single global variable or function.

As a bonus, you can choose the phase where the event is fired, attach only-once-triggering events to elements without extra work, and get better performance of scrolling with certain events.


There is another consideration. Consider the following code.

<!DOCTYPE html>
<title>Answer</title>
<script type=module>function f() {alert(1)}</script>
<input type=button value=1 onclick=f()>
<input type=button value=2 id=button>
<script type=module>button.onclick = function () {alert(2)}</script>

f is undefined in onclick for the button with value=1 (because f is defined in a module). So, when an event handler must execute code in a module (e.g., code that uses import), the event handler must be assigned in the module.


When the code is called from an on-event handler, its this is set to the DOM element on which the listener is placed:

<!-- this will be the button -->
<button onclick="alert(this.tagName.toLowerCase());">
    Show this
</button>

Note however that only the outer code has its this set this way. In this case, the inner function's this isn't set so it returns the global/window object (i.e. the default object in non–strict mode where this isn't set by the call).

Check this in the global context and in function context.

<!-- this will be the global object which is window in the browser -->
<button onclick="alert((function() { return this; })());">
    Show inner this
</button>

Another consideration is when you use addEventListener.

Check event listener with an anonymous function and with an arrow function and this in arrow functions.

Please note that while anonymous and arrow functions are similar, they have different this bindings. While anonymous (and all traditional JavaScript functions) create their own this bindings, arrow functions inherit the this binding of the containing context.

<button id="my_button" onclick="alert(this.tagName.toLowerCase());">
    Show this
</button>

<script>
    const btn = document.getElementById("my_button")
    btn.addEventListener("click", function () {
        // this will be the button
        console.log(this)
    })
    btn.addEventListener("click", () => {
        // this will be window
        console.log(this)
    })
</script>

Here is a simple article I wrote about the JavaScript this keyword but it doesn't mention event handler.