问题
Please, consider the following code.
<!DOCTYPE html>
<title>Question</title>
<script>
function report(handler, _this) {
alert(
"handler:\n" + handler + "\n" +
"this: " + _this + "\n" +
"event: " + event + "\n"
)
}
</script>
<input type=button id=A value=A onclick="report(A.onclick, this)">
<script>function f() {report(B.onclick, this)}</script>
<input type=button id=B value=B onclick="f()">
<input type=button id=C value=C>
<script>C.onclick = function () {report(C.onclick, this)}</script>
By clicking the buttons, I have seen that:
A.onclick
andB.onclick
are wrapped in "function onclick(event) {...}".- In
B.onclick
,this
is the window (rather than a button).
Are there any other considerations?
回答1:
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.
回答2:
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.
回答3:
There are no particular benefits in setting event listeners in HTML. Moreover it is considered harmful, e.g. CSP bans it https://developers.google.com/web/fundamentals/security/csp#inline_code_is_considered_harmful
回答4:
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 ancestors 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 onxxxx
or 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, 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.
来源:https://stackoverflow.com/questions/62462554/how-does-the-way-in-which-a-javascript-event-handler-is-assigned-affect-its-exec