可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
What's the difference between addEventListener
and onclick
?
var h = document.getElementById("a");
h.onclick = dothing1;
h.addEventListener("click", dothing2);
The code above resides together in a separate .js file, and they both work perfectly.
回答1:
Both are correct, but none of them are "best" per se, and there may be a reason the developer chose to use both approaches.
Event Listeners (addEventListener and IE's attachEvent)
Earlier versions of Internet Explorer implement javascript differently from pretty much every other browser. With versions less than 9, you use the attachEvent
[doc] method, like this:
element.attachEvent('onclick', function() { /* do stuff here*/ });
In most other browsers (including IE 9 and above), you use addEventListener
[doc], like this:
element.addEventListener('click', function() { /* do stuff here*/ }, false);
Using this approach (DOM Level 2 events), you can attach a theoretically unlimited number of events to any single element. The only practical limitation is client-side memory and other performance concerns, which are different for each browser.
The examples above represent using an anonymous function[doc]. You can also add an event listener using a function reference[doc] or a closure[doc]:
var myFunctionReference = function() { /* do stuff here*/ }
element.attachEvent('onclick', myFunctionReference);
element.addEventListener('click', myFunctionReference , false);
Another important feature of addEventListener
is the final parameter, which controls how the listener reacts to bubbling events[doc]. I've been passing false in the examples, which is standard for probably 95% of use cases. There is no equivalent argument for attachEvent
, or when using inline events.
Inline events (HTML onclick="" property and element.onclick)
In all browsers that support javascript, you can put an event listener inline, meaning right in the HTML code. You've probably seen this:
<a id="testing" href="#" onclick="alert('did stuff inline');">Click me</a>
Most experienced developers shun this method, but it does get the job done; it is simple and direct. You may not use closures or anonymous functions here (though the handler itself is an anonymous function of sorts), and your control of scope is limited.
The other method you mention:
element.onclick = function () { /*do stuff here */ };
... is the equivalent of inline javascript except that you have more control of the scope (since you're writing a script rather than HTML) and can use anonymous functions, function references, and/or closures.
The significant drawback with inline events is that unlike event listeners described above, you may only have one inline event assigned. Inline events are stored as an attribute/property of the element[doc], meaning that it can be overwritten.
Using the example <a>
from the HTML above:
var element = document.getElementById('testing');
element.onclick = function () { alert('did stuff #1'); };
element.onclick = function () { alert('did stuff #2'); };
... when you clicked the element, you'd only see "Did stuff #2" - you overwrote the first assigned of the onclick
property with the second value, and you overwrote the original inline HTML onclick
property too. Check it out here: http://jsfiddle.net/jpgah/.
Broadly speaking, do not use inline events. There may be specific use cases for it, but if you are not 100% sure you have that use case, then you do not and should not use inline events.
Modern Javascript (Angular and the like)
Since this answer was originally posted, javascript frameworks like Angular have become far more popular. You will see code like this in an Angular template:
<button (click)="doSomething()">Do Something</button>
This looks like an inline event, but it isn't. This type of template will be transpiled into more complex code which uses event listeners behind the scenes. Everything I've written about events here still applies, but you are removed from the nitty gritty by at least one layer. You should understand the nuts and bolts, but if your modern JS framework best practices involve writing this kind of code in a template, don't feel like you're using an inline event -- you aren't.
Which is Best?
The question is a matter of browser compatibility and necessity. Do you need to attach more than one event to an element? Will you in the future? Odds are, you will. attachEvent and addEventListener are necessary. If not, an inline event may seem like they'd do the trick, but you're much better served preparing for a future that, though it may seem unlikely, is predictable at least. There is a chance you'll have to move to JS-based event listeners, so you may as well just start there. Don't use inline events.
jQuery and other javascript frameworks encapsulate the different browser implementations of DOM level 2 events in generic models so you can write cross-browser compliant code without having to worry about IE's history as a rebel. Same code with jQuery, all cross-browser and ready to rock:
$(element).on('click', function () { /* do stuff */ });
Don't run out and get a framework just for this one thing, though. You can easily roll your own little utility to take care of the older browsers:
function addEvent(element, evnt, funct){
if (element.attachEvent)
return element.attachEvent('on'+evnt, funct);
else
return element.addEventListener(evnt, funct, false);
}
// example
addEvent(
document.getElementById('myElement'),
'click',
function () { alert('hi!'); }
);
Try it: http://jsfiddle.net/bmArj/
Taking all of that into consideration, unless the script you're looking at took the browser differences into account some other way (in code not shown in your question), the part using addEventListener
would not work in IE versions less than 9.
Documentation and Related Reading
- W3 HTML specification, element Event Handler Attributes
- element.addEventListener on MDN
- element.attachEvent on MSDN
- Jquery.on
- quirksmode blog "Introduction to Events"
- CDN-hosted javascript libraries at Google
回答2:
The difference you could see if you had another couple of functions:
var h = document.getElementById('a');
h.onclick = doThing_1;
h.onclick = doThing_2;
h.addEventListener('click', doThing_3);
h.addEventListener('click', doThing_4);
Functions 2, 3 and 4 work, but 1 does not. This is because addEventListener
does not overwrite existing event handlers, whereas onclick
overrides any existing onclick = fn
event handlers.
The other significant difference, of course, is that onclick
will always work, whereas addEventListener
does not work in Internet Explorer before version 9. You can use the analogous attachEvent
(which has slightly different syntax) in IE <9.
回答3:
In this answer I will describe the three methods of defining DOM event handlers.
element.addEventListener()
Code example:
const element = document.querySelector('a');
element.addEventListener('click', event => event.preventDefault(), true);
<a href="//google.com">Try clicking this link.</a>
element.addEventListener()
has multiple advantages:
- Allows you to register unlimited events handlers and remove them with
element.removeEventListener()
.
- Has
useCapture
parameter, which indicates whether you'd like to handle event in its capturing or bubbling phase. See: Unable to understand useCapture attribute in addEventListener.
- Cares about semantics. Basically, it makes registering event handlers more explicit. For a beginner, a function call makes it obvious that something happens, whereas assigning event to some property of DOM element is at least not intuitive.
- Allows you to separate document structure (HTML) and logic (JavaScript). In tiny web applications it may not seem to matter, but it does matter with any bigger project. It's way much easier to maintain a project which separates structure and logic than a project which doesn't.
- Eliminates confusion with correct event names. Due to using inline event listeners or assigning event listeners to
.onevent
properties of DOM elements, lots of inexperienced JavaScript programmers thinks that the event name is for example onclick
or onload
. on
is not a part of event name. Correct event names are click
and load
, and that's how event names are passed to .addEventListener()
.
- Works in almost all browser. If you still have to support IE <= 8, you can use a polyfill from MDN.
element.onevent = function() {}
(e.g. onclick
, onload
)
Code example:
const element = document.querySelector('a');
element.onclick = event => event.preventDefault();
<a href="//google.com">Try clicking this link.</a>
This was a way to register event handlers in DOM 0. It's now discouraged, because it:
- Allows you to register only one event handler. Also removing the assigned handler is not intuitive, because to remove event handler assigned using this method, you have to revert
onevent
property back to its initial state (i.e. null
).
- Doesn't respond to errors appropriately. For example, if you by mistake assign a string to
window.onload
, for example: window.onload = "test";
, it won't throw any errors. Your code wouldn't work and it would be really hard to find out why. .addEventListener()
however, would throw error (at least in Firefox): TypeError: Argument 2 of EventTarget.addEventListener is not an object.
- Doesn't provide a way to choose if you want to handle event in its capturing or bubbling phase.
Inline event handlers (onevent
HTML attribute)
Code example:
<a href="//google.com" onclick="event.preventDefault();">Try clicking this link.</a>
Similarly to element.onevent
, it's now discouraged. Besides the issues that element.onevent
has, it:
- Is a potential security issue, because it makes XSS much more harmful. Nowadays websites should send proper
Content-Security-Policy
HTTP header to block inline scripts and allow external scripts only from trusted domains. See How does Content Security Policy work?
- Doesn't separate document structure and logic.
- If you generate your page with a server-side script, and for example you generate a hundred links, each with the same inline event handler, your code would be much longer than if the event handler was defined only once. That means the client would have to download more content, and in result your website would be slower.
See also
EventTarget.addEventListener()
documentation (MDN)
EventTarget.removeEventListener()
documentation (MDN)
- onclick vs addEventListener
- dom-events tag wiki
回答4:
While onclick
works in all browsers, addEventListener
does not work in older versions of Internet Explorer, which uses attachEvent
instead.
The downside of onclick
is that there can only be one event handler, while the other two will fire all registered callbacks.
回答5:
As far as I know, the DOM "load" event still does only work very limited. That means it'll only fire for the window object
, images
and <script>
elements for instance. The same goes for the direct onload
assignment. There is no technical difference between those two. Probably .onload =
has a better cross-browser availabilty.
However, you cannot assign a load event
to a <div>
or <span>
element or whatnot.
回答6:
Summary:
addEventListener
can add multiple events, whereas with onclick
this cannot be done.
onclick
can be added as an HTML
attribute, whereas an addEventListener
can only be added within <script>
elements.
addEventListener
can take a third argument which can stop the event propagation.
Both can be used to handle events. However, addEventListener
should be the preferred choice since it can do everything onclick
does and more. Don't use inline onclick
as HTML attributes as this mixes up the javascript and the HTML which is a bad practice. It makes the code less maintainable.
回答7:
One detail hasn't been noted yet: modern desktop browsers consider different button presses to be "clicks" for AddEventListener('click'
and onclick
by default.
- On Chrome 42 and IE11, both
onclick
and AddEventListener
click fire on left and middle click.
- On Firefox 38,
onclick
fires only on left click, but AddEventListener
click fires on left, middle and right clicks.
Also, middle-click behavior is very inconsistent across browsers when scroll cursors are involved:
- On Firefox, middle-click events always fire.
- On Chrome, they won't fire if the middleclick opens or closes a scroll cursor.
- On IE, they fire when scroll cursor closes, but not when it opens.
It is also worth noting that "click" events for any keyboard-selectable HTML element such as input
also fire on space or enter when the element is selected.
回答8:
According to MDN, the difference is as below:
addEventListener:
The EventTarget.addEventListener() method adds the specified
EventListener-compatible object to the list of event listeners for the
specified event type on the EventTarget on which it's called. The
event target may be an Element in a document, the Document itself, a
Window, or any other object that supports events (such as
XMLHttpRequest).
onclick:
The onclick property returns the click event handler code on the
current element. When using the click event to trigger an action, also
consider adding this same action to the keydown event, to allow the
use of that same action by people who don't use a mouse or a touch
screen. Syntax element.onclick = functionRef; where functionRef is a
function - often a name of a function declared elsewhere or a function
expression. See "JavaScript Guide:Functions" for details.
There is also a syntax difference in use as you see in the below codes:
addEventListener:
// Function to change the content of t2
function modifyText() {
var t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
}
}
// add event listener to table
var el = document.getElementById("outside");
el.addEventListener("click", modifyText, false);
onclick:
function initElement() {
var p = document.getElementById("foo");
// NOTE: showAlert(); or showAlert(param); will NOT work here.
// Must be a reference to a function name, not a function call.
p.onclick = showAlert;
};
function showAlert(event) {
alert("onclick Event detected!");
}
回答9:
If you are not too worried about browser support, there is a way to rebind the 'this' reference in the function called by the event. It will normally point to the element that generated the event when the function is executed, which is not always what you want. The tricky part is to at the same time be able to remove the very same event listener, as shown in this example: http://jsfiddle.net/roenbaeck/vBYu3/
/*
Testing that the function returned from bind is rereferenceable,
such that it can be added and removed as an event listener.
*/
function MyImportantCalloutToYou(message, otherMessage) {
// the following is necessary as calling bind again does
// not return the same function, so instead we replace the
// original function with the one bound to this instance
this.swap = this.swap.bind(this);
this.element = document.createElement('div');
this.element.addEventListener('click', this.swap, false);
document.body.appendChild(this.element);
}
MyImportantCalloutToYou.prototype = {
element: null,
swap: function() {
// now this function can be properly removed
this.element.removeEventListener('click', this.swap, false);
}
}
The code above works well in Chrome, and there's probably some shim around making "bind" compatible with other browsers.
回答10:
Using inline handlers is incompatible with Content Security Policy so the addEventListener
approach is more secure from that point of view. Of course you can enable the inline handlers with unsafe-inline
but, as the name suggests, it's not safe as it brings back the whole hordes of JavaScript exploits that CSP prevents.
回答11:
It should also be possible to either extend the listener by prototyping it (if we have a reference to it and its not an anonymous function) -or make the 'onclick' call a call to a function library (a function calling other functions)
like
elm.onclick = myFunctionList
function myFunctionList(){
myFunc1();
myFunc2();
}
this means we never has to chenge the onclick call just alter the function myFunctionList() to do what ever we want, but this leave us without control of bubbling/catching phases so should be avoided for newer browsers.
just in case someone find this thread in the future...
回答12:
Javascript tends to blend everything into objects and that can make it confusing. All into one is the JavaScript way.
Essentially onclick is a HTML attribute. Conversely addEventListener is a method on the DOM object representing a HTML element.
In JavaScript objects, a method is merely a property that has a function as a value and that works against the object it is attached to (using this for example).
In JavaScript as HTML element represented by DOM will have it's attributes mapped onto its properties.
This is where people get confused because JavaScript melds everything into a single container or namespace with no layer of indirection.
In a normal OO layout (which does at least merge the namespace of properties/methods) you would might have something like:
domElement.addEventListener // Object(Method)
domElement.attributes.onload // Object(Property(Object(Property(String))))
There are variations like it could use a getter/setter for onload or HashMap for attributes but ultimately that's how it would look. JavaScript eliminated that layer of indirection at the expect of knowing what's what among other things. It merged domElement and attributes together.
Barring compatibility you should as a best practice use addEventListener. As other answers talk about the differences in that regard rather than the fundamental programmatic differences I will forgo it. Essentially, in an ideal world you're really only meant to use on* from HTML but in an even more ideal world you shouldn't be doing anything like that from HTML.
Why is it dominant today? It's quicker to write, easier to learn and tends to just work.
The whole point of onload in HTML is to give access to the addEventListener method or functionality in the first place. By using it in JS you're going through HTML when you could be applying it directly.
Hypothetically you can make your own attributes:
$('[myclick]').each(function(i, v) {
v.addEventListener('click', function() {
eval(v.myclick); // eval($(v).attr('myclick'));
});
});
What JS does with is a bit different to that.
You can equate it to something like (for every element created):
element.addEventListener('click', function() {
switch(typeof element.onclick) {
case 'string':eval(element.onclick);break;
case 'function':element.onclick();break;
}
});
The actual implementation details will likely differ with a range of subtle variations making the two slightly different in some cases but that's the gist of it.
It's arguably a compatibility hack that you can pin a function to an on attribute since by default attributes are all strings.
回答13:
addEventListener
lets you set multiple handlers, but isn't supported in IE8 or lower.
IE does have attachEvent
, but it's not exactly the same.
回答14:
The context referenced by 'this'
keyword in JavasSript is different.
look at the following code:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<input id="btnSubmit" type="button" value="Submit" />
<script>
function disable() {
this.disabled = true;
}
var btnSubmit = document.getElementById('btnSubmit');
btnSubmit.onclick = disable();
//btnSubmit.addEventListener('click', disable, false);
</script>
</body>
</html>
What it does is really simple. when you click the button, the button will be disabled automatically.
First when you try to hook up the events in this way button.onclick = function(),
onclick event will be triggered by clicking the button, however, the button will not be disabled because there's no explicit binding between button.onclick and onclick event handler. If you debug see the 'this'
object, you can see it refers to 'window'
object.
Secondly, if you comment btnSubmit.onclick = disable();
and uncomment
//btnSubmit.addEventListener('click', disable, false);
you can see that the button is disabled because with this way there's explicit binding between button.onclick event and onclick event handler. If you debug into disable function, you can see 'this'
refers to the button control
rather than the window
.
This is something I don't like about JavaScript which is inconsistency.
Btw, if you are using jQuery($('#btnSubmit').on('click', disable);
), it uses explicit binding.