How do multiple addEventListener work in JavaScript?

user1643156 picture user1643156 · Apr 29, 2013 · Viewed 22.1k times · Source

There are 2 scripts in a document

// my_script.js goes first
document.onclick = function() {
    alert("document clicked");
};

// other_script.js comes after
// this overrides the onclick of my script,
// and alert will NOT be fired
document.onclick = function() {
    return false;
};

To make sure my click event does not get overridden by other script, I switched to addEventListener.

// my_script.js goes first
document.addEventListener("click", function() {
    alert("document clicked");
}, false);

// other_script.js comes after
document.addEventListener("click", function() {
    return false;
}, false);

Now I got another question. Since return false in the second code is defined after alert, how come it does not prevent alert from being called?

What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?

Answer

T.J. Crowder picture T.J. Crowder · Apr 29, 2013

What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?

If you can register your handler first, before they do, you can do that, provided the browser you're using correctly implements DOM3 events (which it probably does unless it's IE8 or earlier).

There are (at least) four things involved here:

  1. Preventing the default.

  2. Stopping propagation to ancestor elements.

  3. Stopping other handlers on the same element from being called.

  4. The order in which handlers are called.

In order:

1. Preventing the default

This is what return false from a DOM0 handler does. (Details: The Story on Return False.) The equivalent in DOM2 and DOM3 is preventDefault:

document.addEventListener("click", function(e) {
    e.preventDefault();
}, false);

Preventing the default may not be all that relevant to what you're doing, but since you were using return false in your DOM0 handler, and that prevents the default, I'm including it here for completeness.

2. Stopping propagation to ancestor elements

DOM0 handlers have no way to do this. DOM2 ones do, via stopPropagation:

document.addEventListener("click", function(e) {
    e.stopPropagation();
}, false);

But stopPropagation doesn't stop other handlers on that same element getting called. From the spec:

The stopPropagation method is used prevent further propagation of an event during event flow. If this method is called by any EventListener the event will cease propagating through the tree. The event will complete dispatch to all listeners on the current EventTarget before event flow stops.

(My emphasis.)

3. Stopping other handlers on the same element from being called

Naturally, this didn't come up for DOM0, because there couldn't be other handlers for the same event on the same element. :-)

As far as I'm aware, there's no way to do this in DOM2, but DOM3 gives us stopImmediatePropagation:

document.addEventListener("click", function(e) {
    e.stopImmediatePropagation();
}, false);

Some libraries offer this feature (even on non-DOM3 systems like IE8) for handlers hooked up via the library, see below.

4. The order in which handlers are called

Again, not something that related to DOM0, because there couldn't be other handlers.

In DOM2, the specification explicitly says that the order in which the handlers attached to an element are called is not guaranteed; but DOM3 changes that, saying that handlers are called in the order in which they're registered.

First, from DOM2 Section 1.2.1:

Although all EventListeners on the EventTarget are guaranteed to be triggered by any event which is received by that EventTarget, no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget.

But this is superceded by DOM3 Section 3.1:

Next, the implementation must determine the current target's candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration.

(My emphasis.)

Some libraries guarantee the order, provided you hook up the events with the library.

It's also worth noting that in Microsoft's predecessor to DOM2 (e.g., attachEvent), it was the opposite of DOM3's order: The handlers were called in reverse order of registration.


So taking #3 and #4 together, if you can register your handler first, it will get called first, and you can use stopImmediatePropagation to prevent other handlers getting called. Provided the browser implements DOM3 correctly.


All of this (including the fact that IE8 and earlier don't even implement DOM2 events, much less DOM3) is one reason people use libraries like jQuery, some of which do guarantee the order (as long as everything is hooking up their handlers via the library in question) and offer ways to stop even other handlers on the same element getting called. (With jQuery, for instance, the order is the order in which they were attached, and you can use stopImmediatePropagation to stop calls to other handlers. But I'm not trying to sell jQuery here, just explaining that some libs offer more functionality than the basic DOM stuff.)