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.

Leave a Comment