There are very big differences.
Event handlers that are set up via HTML event attributes were the first way we did event handling - - before there was a DOM. This way of setting up events became known as DOM Level 0 (as in, the de facto standard before there was a standard). When this was the way to do it (circa 1995), it was fine, because we had no other choice. But, the way the attribute value was turned into event handling code was handled like this:
The HTML element has an event attribute declared on it and that attribute has a value that is the JavaScript code that should be executed when the event occurs:
<input type="button" onclick="alert('You clicked me!')" value="Click me">
Notice that the value of onclick
isn't a function reference, just loose code to run.
This is actually implemented by the browser by creating a function in the Global scope that acts as a wrapper for the supplied code. We can see that here:
// Output the value of the onclick property.
// Note the value supplied in the HTML
// is wrapped in a function that we didn't create
alert(document.querySelector("input").onclick);
<input type="button" onclick="alert('You clicked me!')" value="Click me">
Because of this automatic wrapping of the attribute value in a Global wrapper function, non-intuitive things often happened like this:
function foo(){
// This function is invoked by clicking the HTML input element
// so, we may reasonably expect that "this" would reference that
// element. But, as you'll see, it doesn't.
alert("You clicked the " + this.nodeName + " element.");
}
<input type="button" onclick="foo()" value="Click me">
The above reports undefined
because in actuality, this
in that context is referring to the Global window
object, which doesn't have a nodeName
property. But, if you didn't know about the Global wrapper (and why would you), this would be very confusing because this
used by a DOM event handler almost always references the DOM element that caused the event to fire.
When the DOM Level 1 Event Handling specification came out (1998), a new way of configuring events came about as well. We now had objects that represented the HTML elements and each object had properties that mapped to the attributes of the element. For this reason many people (up to this very day) still believe that working with attributes or object properties is largely the same thing. But, there are important differences (which I've written about in this other answer of mine: see second half of answer) as attributes are used to set values, which can affect state, but properties are used to override attributes and set state.
So, with DOM event handling, we'd do the following, which you will see, sets up the event callback, not as loose code to be executed, but by storing a reference to a function to be invoked when the event occurs. Because we supply the function, it has the scope of the DOM object we store it with and we no longer need to wrap loose commands with a Global. This causes this
binding to work as expected:
// Just a reference to a function is used with event
// properties. Not "loose" code. And, because the function
// is actually being stored with the DOM element, this binding
// works as expected.
document.querySelector("input").onclick = foo;
function foo(){
// This function is invoked by clicking the HTML input element
// so, we may reasonably expect that "this" would reference that
// element. But, as you'll see, it doesn't.
alert("You clicked the " + this.nodeName + " element.");
}
<input type="button" value="Click me">
The additional benefit of DOM Event handling was that we kept the JavaScript stuff separate from the HTML stuff (ie. separation of concerns). This was a benefit, but not the driving force for the change.
Now, having explained the differences between those two mechanisms for registering events, the story isn't complete. There is still an problem with DOM event properties in that if you wanted to set up multiple event handlers, you didn't have a clean way to do that since you can only store one function reference in a given property. So, with modern event handling, we use .addEventListener()
, which allows us to register as many event callbacks with an event that we want and we get the added benefit of knowing that the callbacks we register will be invoked in the order that we registered them.
// Register an event listener:
document.querySelector("input").addEventListener("click", foo1);
// Register more event listeners:
document.querySelector("input").addEventListener("click", foo3);
document.querySelector("input").addEventListener("click", foo2);
function foo1(){ console.log("Hello from foo1"); }
function foo2(){ console.log("Hello from foo2"); }
function foo3(){ console.log("Hello from foo3"); }
<input type="button" value="Click me">