Event Listeners
Learn to attach and detach event handlers with addEventListener and removeEventListener, read details off the event object, and control listener behavior with the options argument (capture, once, passive).
addEventListener is the modern, standard way to hook JavaScript code up to an event. It replaced older techniques like inline onclick="..." attributes and the element.onclick = fn property because it can attach multiple independent listeners to the same event, gives fine-grained control over how the listener behaves, and can be cleanly removed later.
Registering listeners with addEventListener
addEventListener(type, listener, options) is called on the target you want to listen on — an element, document, or window. type is the event name as a string (no "on" prefix), and listener is the function to call when the event fires.
Unlike setting element.onclick = fn (which can only ever hold one handler, overwriting any previous one), addEventListener lets you attach as many independent listeners to the same event on the same element as you want — each one fires, in the order it was registered, without interfering with the others.
The event object
Every listener function is automatically called with a single argument: the event object. It carries details about what happened and offers methods to control what happens next.
Two of the most important properties are event.target, the actual element the event originated on (for example, the specific button inside a container that was clicked), and event.currentTarget, the element the listener is currently attached to — these can differ when an event bubbles up from a descendant.
• event.type — the event name, e.g. "click" • event.target — the exact element the event originated from • event.currentTarget — the element the listener is attached to • event.preventDefault() — cancels the browser's default action for this event, when cancelable • event.stopPropagation() — stops the event from continuing to bubble/capture further • event.key / event.code — for keyboard events, which key was involved
Removing listeners with removeEventListener
removeEventListener(type, listener, options) detaches a previously attached listener. Critically, it must be called with a reference to the exact same function that was passed to addEventListener — an anonymous function (or an arrow function defined inline) can never be removed, because there is no way to refer back to it.
This is why code that needs to remove a listener later should always store the handler in a named variable or function declaration first, and pass that same reference to both addEventListener and removeEventListener.
Listener options: capture, once, passive
The third argument to addEventListener can be a boolean (shorthand for the capture option) or an options object with three useful flags: capture, once, and passive.
capture: true makes the listener fire during the capturing phase (as the event travels down toward the target) instead of the default bubbling phase. once: true automatically removes the listener after it fires a single time — handy for one-off interactions like dismissing a welcome banner. passive: true tells the browser the listener will never call preventDefault(), which lets the browser start scrolling immediately instead of waiting to see if the handler cancels it — this is important for smooth performance on touchmove and wheel listeners.
Multiple listeners and identical registrations
If you call addEventListener with the exact same type, the exact same function reference, and the exact same capture value more than once, the browser silently ignores the duplicate — the listener is only ever registered once. This is a safe default that prevents accidental double-firing when setup code runs more than once.
addEventListener and removeEventListener signatures
listener must be the same function reference in both calls for removeEventListener to work. options is optional; omitting it uses the defaults shown above (bubbling phase, fires every time, not passive). Passing true instead of an options object is shorthand for { capture: true }.
target.addEventListener(type, listener, options);
target.removeEventListener(type, listener, options);
// options can be:
{ capture: false, once: false, passive: false }Code Examples
handleLike is stored as a named function so it can be referenced again later, rather than being written inline as an anonymous arrow function. Expected result: Likes: 1 target: like-btn
Because handleLike is a named reference, it can later be passed to removeEventListener to detach it.
With { once: true }, the browser automatically removes the listener right after it fires the first time, so clicking the (now-removed) button again does nothing — no manual removeEventListener call is needed for one-time interactions. Expected result: Banner dismissed, listener auto-removed
{ once: true } is the cleanest way to express "run this exactly one time."
This listener is registered with capture: true (the shorthand true), so it runs during the capturing phase before the click reaches its target. When the overlay itself (not its inner content) is clicked, the modal closes and the listener removes itself, matching how it was added — same function reference, same capture value.
removeEventListener must match type, function reference, AND the capture flag used when adding it.
Common mistake: Calling document.removeEventListener("click", handleOutsideClick) without the trailing true would silently fail to remove this particular listener, because it was registered with capture: true.
const button = document.querySelector("#like-btn");
let likes = 0;
function handleLike(event) {
likes++;
console.log("Likes:", likes, "target:", event.target.id);
}
button.addEventListener("click", handleLike);const banner = document.querySelector("#welcome-banner");
const closeBtn = document.querySelector("#close-banner");
function dismissBanner() {
banner.remove();
console.log("Banner dismissed, listener auto-removed");
}
closeBtn.addEventListener("click", dismissBanner, { once: true });const modal = document.querySelector("#modal");
function handleOutsideClick(event) {
if (event.target === modal) {
modal.classList.remove("open");
document.removeEventListener("click", handleOutsideClick, true);
}
}
document.addEventListener("click", handleOutsideClick, true);Important Rules
addEventListener can attach many independent listeners to the same event on the same element.
removeEventListener only works with a stored, named function reference — never with an inline anonymous function.
The capture value must match between addEventListener and removeEventListener for removal to succeed.
{ once: true } removes a listener automatically after it fires once; { passive: true } tells the browser it will never call preventDefault().
Where this shows up
Single-page apps routinely add and remove listeners as components mount and unmount, to avoid memory leaks and "zombie" handlers firing on elements that are no longer visible. Libraries and frameworks manage this automatically, but any hand-rolled widget — a custom dropdown, a modal, a drag handle — needs the same discipline: keep a reference to every listener you might need to remove later.
button.addEventListener("click", function () { doSomething(); }); // later: button.removeEventListener("click", function () { doSomething(); }); // does NOT work function doSomething() { /*... */ } button.addEventListener("click", doSomething); // later: button.removeEventListener("click", doSomething);
Each function () { ... } literal creates a brand-new function object, even if the code inside looks identical. removeEventListener compares function references, so the second anonymous function is never seen as "the same" as the first — the original listener is never removed.
[Multiple Choice] Why does removeEventListener fail to remove a listener that was added as an inline anonymous function? A. removeEventListener only works on window and document B. Anonymous functions cannot be passed as arguments in JavaScript C. Each anonymous function literal is a distinct function object, so there is no reference left to match against D. addEventListener does not support anonymous functions
C. Each anonymous function literal is a distinct function object, so there is no reference left to match against — removeEventListener compares the exact function reference. A new anonymous function written at removal time is a different object in memory from the one originally registered, even with identical code.
[True / False] Passing { once: true } to addEventListener means the listener fires only the first time the event occurs and is then automatically removed. A. True B. False
A. True — True — { once: true } causes the browser to detach the listener immediately after its first invocation.
Write a function trackFirstClick that logs "First click registered" the first time (and only the first time) any part of the document is clicked, without manually calling removeEventListener.
function trackFirstClick() { console.log("First click registered"); } document.addEventListener("click", trackFirstClick, { once: true });
Use the { once: true } option on document.addEventListener.