7

I am curious about how the content of the inline element attribute event works under the hood.

We start off with a simple function

function handler(e) {
    console.log(e);
}

Use Case 1

If we would want to add this handler to our button we could do:

<button onclick="handler();">Click Me</button>

Which Logs on click: undefined

Fair enough, we called a function without passing arguments.

Use Case 2

If we now change the button to take the event as an argument:

<button onclick="handler(event);">Click Me</button>

We log on click: MouseEvent{ ... }

Now we log the event, as we could have expected

Use Case 3

But what if we just specify the function name?

<button onclick="handler">Click Me</button>

Nothing happens!, I was hoping just giving a reference to a function, would do the trick. So when "onlick" would happen, the referred function would be called with the proper event argument.

Use Case 4

When we add the handler in javascript:

const btn = document.getElementById('btn');
btn.onclick = handler;

it works, capturing the event and logs the related event object.

Concluding Question

Whatever text/content/statement that we pass to onclick="" inline, does it basically run eval() on it? And when we attach it in javascript (btn.onclick = ...) it goes through the proper process to intercept the event and then call bounded handler function and passes the corresponding event object with it (e.g. MouseEvent)?

Quick Note

To be clear, this is an informative question, to understand the fundamentals, disregarding if it's considered a bad or good practice. That's not being discussed at this point.

Segers-Ian
  • 1,027
  • 10
  • 21
  • 1
    I don't think that happens - look [here](https://stackoverflow.com/q/20485425/10221765) – Jack Bashford May 23 '19 at 08:34
  • 1
    Keep in mind that [horrible things with `with` are done to the function](https://stackoverflow.com/questions/31613748/why-cant-i-call-a-function-named-clear-from-an-onclick-attribute), so what happens to the attribute isn't simple. – Quentin May 23 '19 at 08:36
  • Use case 3, nothing happens because onclick takes a bit of javascript to execute, not a name - so what happens is “handler” is interpreted as a variable name (just as if you had it as a stand-alone line in a .js file), which is undefined so evaluates as false and does nothing. – racraman May 23 '19 at 08:38

3 Answers3

5

Yes, in an inline handler, the attribute text is essentially just evaled, with a few qualifications. What

<button onclick="handler">Click Me</button>

is roughly equivalent to would be if you had a (true) handler which referenced the handler function but didn't call it, for example:

btn.onclick = () => {
  handler;
};

This doesn't throw an error (because handler is referenceable), but it doesn't run handler either, because you're only referencing the function, not calling it.

When you do

btn.onclick = handler;

you are passing a reference to handler to the onclick internals, which then call that reference later, when the button is clicked.

(If you had done btn.onclick = handler;, handler would be invoked immediately, and its return value would be added as a listener, which isn't what you'd want)

When you do

<button onclick="handler(event);">Click Me</button>

the event argument you're passing comes from window.event, which is why it's referenceable.

Like I said, there are a few qualifications, though: inline handlers implicitly use something like a with(this) around the whole text that is run, where this is the element in question. (it also uses with for the document and containing form, as you can see from Quentin's answer) For example:

<a onclick="search();">Click me!</div>

looks, to the interpreter, a bit like:

<a onclick="
  with(this) {
    search();
  }
">Click me!</div>

which is problematic, because search is a property of HTMLAnchorElement.prototype. Referencing other variable names can have similar problems, if those variable names are properties anywhere on the element's prototype chain.

Best to avoid inline handlers entirely, and attach the event properly using Javascript instead.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Yeah, `this` is indeed tricky as you can trigger a lookup within the prototype chain of the targetted element. But seems my suspicion is confirmed with your answer. My theory was that `onclick="handler"` didn't trigger because of it's (somewhat) "eval" behavior. So it would just have the reference but not "know" to call the referenced thing (this case being a function). Then I guess my gut feeling had me on the right track. Found also in some docs that the argument expects "script" which does not mean it expects a function as an argument but actually a script as a string to "eval" sort to say – Segers-Ian May 23 '19 at 14:02
2

This is not what happens, but you can imagine it like this:

Imagine that the javascript engines saves the content of the onclick attribute as a string, and when a user clicks the element, then evaluates the content.

For example:

function foo(a, b) {
  alert(a + ' ' + b);
}
<button onclick="foo('hello', 'world')">click me</button>

<button onclick="(function () { alert ('I\'m an inline IIFE') })()">click me</button>

It's the same result of eval("foo('hello', 'world')". What if you pass event or this or other keywords?

Well, also here, you can imagine a javascript engines doing this:

var this = /* create context */;
var event = /* create event */;
eval("foo(this, event)");

This is what the specification says, the attribute needs to be evaluated! I'm not saying that the engine uses eval :P

Example:

<button onclick="alert('hello world')">Click me</button>

What about btn.onclick = handler;?

This is another pair of shoes. The onclick property is a callback, and it wants a function reference.

1

When you use HTML attribute for the event, you actually create a function under the hood which assigned as the handler of the relevant event.

  • The body of the function is the content of the attribute. It should be JavaScript code.
  • The function gets as parameter the event parameter

So,

In case 1, The generated handler :

function handlerHtml(event) {
    handler()
}

In case 2, The generated handler :

function handlerHtml(event) {
    handler(event)
}

In case 3, The generated handler :

function handlerHtml(event) {
    handler
}
napuzba
  • 6,033
  • 3
  • 21
  • 32