Event listeners make web pages interactive. They let your
JavaScript code respond when the user clicks a button, types
into a field, resizes the window, or performs many other
actions.
In this lesson you will learn how to attach and remove listeners,
how the event object works, mouse and keyboard events, form
events, event bubbling and propagation, preventDefault,
event delegation, window/document events, and custom events.
Adding Event Listeners
The modern and preferred way to attach a listener is
element.addEventListener(type, handler).
What this code does: Register a click handler. When the (simulated)
click occurs, the handler runs and logs a message.
makeElement(tag) is a helper that creates a plain JavaScript object to represent an HTML element. The listeners object is private storage for event handlers registered on this simulated element. The returned object keeps the supplied tag name so the example can identify what kind of element it represents.
addEventListener(type, handler) receives an event name such as click and the function that should run for that event. The expression listeners[type] = listeners[type] || [] creates an array the first time that event type is used, or reuses the existing array when handlers have already been registered. push(handler) adds the new function, allowing more than one handler for the same event.
simulate(type) looks up the handlers registered for the requested event type. If none exist, the fallback empty array prevents an error. forEach calls every registered function and supplies a small event object containing the event type. This is a teaching simulation; a real browser dispatches click events automatically.
makeElement("button") creates the simulated button object. addEventListener("click", …) stores the anonymous function in the click-handler array. The function itself does not run when it is registered; it is saved for later execution.
The handler logs a message when it is called. button.simulate("click") manually invokes the stored click handler, so the console displays Button was clicked!. On a real page, a user clicking the button would cause the browser to dispatch the event.
The Event Object
Every handler receives an event object that describes what
happened (type, target, key, coordinates, etc.).
What this code does: The handler receives an event object. We log
its type and the target element.
makeElement(tag) creates a simulated element and a private listeners object. The returned object stores its tag and exposes methods for registering and simulating events. This lightweight object is used instead of a real HTML element so the event-object behavior can be tested in the console.
addEventListener(type, handler) assigns the supplied function to listeners[type]. Unlike Example 1, this simplified version stores only one handler per event type; registering another handler for the same type would replace the previous one.
simulate(type, extra) first checks whether a handler exists for the requested type. Object.assign creates an event object containing the event type and target, then copies any extra properties onto it. extra || {} ensures that Object.assign receives an object even when no extra data is supplied. The handler is called with this completed event object.
makeElement("button") returns an object whose tag property is button. The variable btn holds that object so the next statements can register a handler and trigger it.
The callback receives the event object as its event parameter. event.type identifies the kind of event, while event.target refers to the simulated element that received it; reading target.tag therefore prints button. Finally, simulate("click") constructs and passes the event object to the callback. Real browser event objects provide many more properties.
Removing Event Listeners
To remove a listener you must pass the same function reference
that was used when adding it. Anonymous functions cannot be
removed later.
What this code does: A named function is added, then removed.
After removal the handler no longer runs.
makeElement(tag) creates a plain object that represents an element. Its listeners object stores arrays of handler functions, and the returned object exposes the element's tag and event methods.
addEventListener(type, handler) creates an array for the requested event type if necessary, then pushes the handler into it. This permits multiple handlers to listen for the same event.
removeEventListener(type, handler) first checks whether that event type has a registered list. filter creates a new array containing every function except the exact function reference passed as handler. The comparison fn !== handler is important: removing a listener requires the same function object that was originally registered, not a newly written function with identical code.
simulate(type) retrieves the handlers for the requested type and calls each one with a basic event object. If the list is empty or missing, the fallback [] means there is nothing to execute.
The simulated button is created, then onClick is declared as a named function. Because the function is stored in a variable, the same function reference can later be passed to removeEventListener. addEventListener registers it for click, and the first simulate call runs it, printing Clicked.
removeEventListener receives the click event name and the exact onClick reference, so filter removes that function from the array. The second simulate call finds no remaining click handlers and prints nothing. This demonstrates why named or otherwise retained function references are useful when listeners must be removed.
Mouse Events
Common mouse events include click, dblclick,
mouseenter, mouseleave, and mousemove.
What this code does: Simulate enter, click, and leave on a button.
Each handler logs a short message.
makeElement(tag) builds a simulated element and a listeners object. The returned object records its tag name and supplies two methods. This is a simplified model of an element, not an actual browser DOM node.
addEventListener stores a handler under its event type. simulate checks whether a handler exists and, if so, calls it with an object whose type property identifies the event. This compact simulation supports one handler per event type.
makeElement("div") creates the simulated card container. The variable card is used for all three mouse-related events below.
The mouseenter handler logs when the pointer enters the card area. The click handler logs when the card is clicked. The mouseleave handler logs when the pointer leaves. Arrow functions are used because each callback is a short one-line action and does not need its own this binding.
The calls simulate mouseenter, click, and mouseleave in that order. Each call finds the matching callback and invokes it, so the console messages appear in the same sequence. On a real webpage these events are generated by pointer movement and clicking rather than by manual simulate calls.
Keyboard Events
keydown and keyup fire when keys are
pressed or released. The event object provides event.key
and event.code.
What this code does: A keydown handler logs the pressed key.
Useful for shortcuts or form controls.
makeElement(tag) creates an object with a tag and a private place to store event handlers. The helper is used to demonstrate keyboard event data without requiring an actual input field in the page.
addEventListener stores the callback under the event type. simulate looks up that callback and passes along the data object supplied by the caller. That data object stands in for the browser's keyboard event object.
The input variable receives the simulated input element. The keydown listener is registered so the callback runs whenever the example simulates a keydown event.
The event parameter contains information about the simulated key press. event.key is logged so the student can see which key was pressed. The strict comparison checks whether the key value is exactly Enter; only then does the code log the form-submission message. This is a common pattern for keyboard shortcuts and submitting forms with Enter.
The first simulation supplies key: a, so the key is logged but the Enter condition is false. The second supplies key: Enter, so both the key message and the special Enter message appear. The two calls demonstrate that the same listener can respond differently based on event-object data.
Form Events
Important form-related events are submit,
input, change, focus, and
blur.
What this code does: Listen for input changes and form submission.
preventDefault stops the real browser navigation/submit.
makeElement(tag) creates a simple object with a tag and a private listeners registry. The returned methods allow the example to register handlers and manually pass event data to them.
addEventListener stores the callback for an event type. simulate retrieves it and calls it with the supplied data; data || {} provides an empty object if the caller omits data.
The example creates two separate simulated elements: form represents the form container, and field represents an input control inside that form. They are separate because input and submit are different event types on different elements.
The input listener receives an event-like object named e. It reads e.value and logs the current value. The later simulation supplies hello as that value, illustrating how an input event can expose the field's current content.
The form listener checks whether the supplied event has a preventDefault method before calling it. In a real browser this method cancels the browser's default submission behavior, such as navigating or reloading according to the form action. The log explains that the custom handler is taking control after preventing that default behavior.
The field simulation sends an input event whose value is hello, so the input handler logs it. The form simulation supplies an object with a preventDefault method. Calling that method sets defaultPrevented to true on the event object, then the handler logs the submission message. This is a mock event object for learning; a real browser creates it.
Event Bubbling
By default most events bubble: after the target handler runs,
the same event travels up through the parent elements.
What this code does: A click on a child also triggers the parent’s
listener because of bubbling.
makeElement accepts a tag and a name, then creates an object with a private listeners registry. The parent property begins as null and will later connect a child element to its parent. This relationship is used to model event bubbling.
addEventListener stores one handler for each event type. dispatch creates a simple event object with the type and target set to the element on which dispatch began. It calls the current element's handler first, then calls dispatch on the parent if one exists. That recursive parent call models an event travelling upward through ancestors.
parent represents a div and child represents a button. Setting child.parent = parent creates the parent-child relationship used by dispatch. Without this assignment, the event would have no parent to bubble to.
The parent handler logs that the parent received the click. The child handler logs that the child received it. Both listen for click, allowing the output order to demonstrate the event's path.
child.dispatch("click") starts the event at the button. The child's handler runs first. Because child.parent refers to parent, dispatch is then called on the parent and its handler runs. The output therefore shows the child message before the parent message, illustrating bubbling. This is a simplified model; native DOM bubbling follows the browser's event propagation path.
stopPropagation()
Calling event.stopPropagation() prevents the event
from continuing to bubble (or capture).
What this code does: The child stops the event, so the parent
handler never runs.
makeElement receives the tag and name, creates a listeners object, and initializes parent to null. The returned object will represent one node in a small simulated element tree.
addEventListener stores the callback for an event type. dispatch creates an event object with its type and a stopped flag initially set to false. Its stopPropagation method changes that flag to true when called by a handler.
If a handler exists on the current element, it receives the event object. After that handler returns, dispatch checks the stopped flag. The event is sent to the parent only when propagation has not been stopped and a parent exists. This conditional is the key difference from the previous bubbling example.
The parent is represented by a div and the child by a button. Assigning child.parent = parent links them so a click dispatched from the child would normally continue to the parent.
The parent callback logs Parent clicked. If the event bubbles up to the parent, this is the message that would appear.
The child callback logs a message explaining that propagation is being stopped, then calls e.stopPropagation(). That method sets e.stopped to true on the same event object that dispatch is using.
The click begins at child, so the child's handler runs and changes the stopped flag. When dispatch reaches its bubbling check, !event.stopped is false, so it does not call parent.dispatch. The parent message is therefore not printed. In a real DOM listener, event.stopPropagation() similarly prevents the event from continuing to ancestor elements.
preventDefault()
Many elements have default browser behavior (links navigate,
forms submit). event.preventDefault() cancels that
behavior.
What this code does: A link click is intercepted and the default
navigation is prevented.
handleLinkClick is a function expression that receives an event object. It first logs that the link was clicked. The next statement reads event.target.href, which represents the destination URL of the link that was activated.
The comment identifies the purpose of the next conditional. Checking event.preventDefault ensures the method exists before it is called. Calling it cancels the normal link navigation, and the following log confirms that the handler prevented that default action. In a real click event, preventDefault must be called during the event handling flow to cancel the browser action.
The final log indicates that the application can now perform its own behavior instead of allowing the normal navigation. The closing brace ends the function expression and its statement.
linkEvent is a plain object standing in for a browser click event. Its type is click, and target represents an anchor element with an href and tagName. The preventDefault method sets defaultPrevented to true on this object, while the initial false value records that the default action has not yet been cancelled.
handleLinkClick(linkEvent) runs the same logic using the mock event object. The handler logs the URL, calls preventDefault, and logs its custom-action message. The final statement reads defaultPrevented and should print true, confirming that the mock default action was cancelled. No real navigation occurs in this console simulation.
Window and Document Events
You can also listen for events on the window and
document objects.
load Event
The load event fires when the entire page has
finished loading.
What this code does: A load handler runs when the page (or a resource)
has finished loading.
// Load event handler
const handlePageLoad = function() {
console.log('Page fully loaded');
console.log('All resources (images, scripts) are ready');
console.log('DOM is ready for manipulation');
};
// In browser:
// window.addEventListener('load', handlePageLoad);
console.log('The load event fires when:');
console.log('- HTML is parsed');
console.log('- All stylesheets are loaded');
console.log('- All images are loaded');
console.log('- All scripts are executed');
handlePageLoad();
This comment identifies the next function as the page-load event handler. Comments are ignored by JavaScript but help readers understand the purpose of the code that follows.
handlePageLoad is a function expression with no parameters because this demonstration does not need to inspect event details. Its three console messages describe the point at which the load event has fired: the page and its dependent resources are ready, and scripts can safely work with the loaded page.
The commented-out line demonstrates the normal browser API: window.addEventListener("load", handlePageLoad). It is commented out so the example can run consistently in a standalone code tester without depending on a real page-load event.
The console messages list what the example considers the load milestone: HTML parsing, stylesheets, images, and scripts. The final handlePageLoad() call invokes the function directly so students can see its output in the test console. This manual call demonstrates the handler's behavior; it is not itself a browser-generated load event.
resize Event
The resize event fires when the browser window is
resized.
What this code does: A resize handler responds when the window size changes.
Useful for responsive layout adjustments.
The comment states that this example demonstrates a window resize event.
handleResize receives an event-like object. It logs a message and reads innerWidth and innerHeight from that object. In a real browser, the resize handler commonly reads window.innerWidth and window.innerHeight; this example passes those dimensions in a mock object.
The commented-out addEventListener line shows how a real page would register the handler on window for resize events. It remains a comment because the example uses manually supplied dimensions instead of relying on the student's actual browser window.
These messages introduce the resize simulation and print a blank line to separate the heading from the results.
sizes is an array of objects. Each object contains an innerWidth and innerHeight pair representing a different viewport size: a large desktop, a smaller laptop, and a portrait tablet-sized viewport. These are sample values, not measurements of the current browser.
forEach visits each object in sizes and passes it to handleResize. The handler therefore logs the dimensions for each simulated resize. The final blank line keeps each result group visually separated in the console.
scroll Event
The scroll event fires when the user scrolls the
page.
What this code does: A scroll handler runs as the user scrolls the page
or a scrollable element.
The comment explains that the following function handles scroll-position information.
handleScroll receives an event-like object and logs that scrolling occurred. It reads scrollY to show the vertical scroll position. The if statement checks whether that position is greater than 100; only positions beyond 100 trigger the additional message. This kind of threshold can be used to show a back-to-top button or change a sticky header.
The commented-out line demonstrates registering handleScroll for the window's scroll event. It is intentionally not executed because the example feeds in known values for predictable output.
The two console statements announce the test and add a blank line before the sample results.
scrollPositions is an array containing four values: the top of the page, a small scroll, and two positions beyond the 100-pixel threshold. These values are used to demonstrate how the handler's conditional responds.
forEach passes each position to the callback as pos. The callback creates a scrollEvent object whose scrollY property holds that value, then calls handleScroll with it. A blank console line separates the output for each position. The additional message appears only for 150 and 300 because those values are greater than 100.
Custom Events
You can create and dispatch custom events using the
CustomEvent constructor.
What this code does: Custom events let you define your own event types
and dispatch them with dispatchEvent.
The comment labels the function as a handler for an application-defined event.
handleCustomEvent receives an event object. It logs a notification, reads event.type to identify the custom event name, and reads event.detail to access application data attached to that event. The detail property is commonly used with browser CustomEvent objects to carry custom information.
This comment separates the handler definition from the object that represents a custom event.
customEventData contains the event type userLoggedIn and a detail object. The detail object carries a username, a timestamp generated by Date.now(), and a message. Date.now() returns the current time in milliseconds since the Unix epoch. This is a plain object used to imitate the shape of a custom event; it is not itself a native Event instance.
The console statements show that the sample event is being created and print its type. The empty string passed to console.log produces a blank line to make the output easier to read.
These commented lines demonstrate the native browser approach: construct a CustomEvent with a name and detail payload, then dispatch it on an element. Because they are comments, they are explanatory only and do not execute in the code tester.
handleCustomEvent receives customEventData, so it logs the event name and the username/timestamp/message object stored in detail. This connects the data object to the handler and demonstrates how custom event payloads are read.
Common Event Types
Event Type
Description
click
Fires when an element is clicked
dblclick
Fires when an element is double-clicked
mouseenter
Fires when the mouse enters an element
mouseleave
Fires when the mouse leaves an element
mousemove
Fires when the mouse moves inside an element
keydown
Fires when a key is pressed down
keyup
Fires when a key is released
submit
Fires when a form is submitted
input
Fires when an input value changes
change
Fires when an input value changes and loses focus
focus
Fires when an element receives focus
blur
Fires when an element loses focus
load
Fires when the page finishes loading
resize
Fires when the window is resized
scroll
Fires when the page is scrolled
Practical Example: Interactive Counter
What this code does: A simple counter: click handlers increment or decrement
a value and log the result.
The comment names the small application being demonstrated.
count starts at zero. This variable stores the current counter value and is shared by all three functions below.
increaseCounter is a function expression. Each time it runs, count++ adds one to the shared value. The function logs the new count and returns it, allowing other code to use the updated number if needed.
decreaseCounter subtracts one from the same count variable with count–. It then logs and returns the updated value. Because all three operations share count, the decrease function changes the value produced by increaseCounter rather than using a separate counter.
resetCounter assigns zero to count regardless of its previous value. It logs the reset value and returns it. This provides a way to restore the application to its initial state.
The comment explains that a real page would attach each function to a button's click event using addEventListener. These lines are commented out because the current example runs in a console without actual button elements.
The console identifies the counter simulation and prints the initial count, which is zero. The blank log line separates the initial state from the actions that follow.
The heading identifies the action being simulated. Calling increaseCounter three times changes count from 0 to 1, then 2, then 3, logging each new value. The blank line separates this group from the decrease action.
The decrease heading identifies the next simulated action. decreaseCounter subtracts one from 3, so the updated count becomes 2 and is logged. Another blank line separates the next action.
The final heading identifies the reset action. resetCounter sets count back to zero and logs the result, demonstrating that the same shared state can be increased, decreased, and reset.
Best Practices for Event Listeners
Use addEventListener() instead of inline event handlers like onclick.
Use named functions instead of anonymous functions when you need to remove event listeners later.
Use event delegation for dynamic content or many child elements.
Always call event.preventDefault() when necessary to prevent unwanted default behavior.
Use event.stopPropagation() carefully to avoid breaking event bubbling when not needed.
Remove event listeners when they are no longer needed to prevent memory leaks.
Be mindful of performance when attaching event listeners to events that fire frequently (like scroll or mousemove).
Use meaningful function names for event handlers.
Common Mistakes With Event Listeners
Trying to remove an anonymous function event listener.
Forgetting to call event.preventDefault() when handling form submissions.
Attaching too many event listeners instead of using event delegation.
Not checking if an element exists before adding an event listener.
Using onclick instead of addEventListener(), which can overwrite previous handlers.
Not understanding event bubbling and propagation.
Quick Summary
Event listeners make web pages interactive by responding to user actions.
Use addEventListener() to attach event listeners to elements.
The event object contains information about the event.
Common mouse events include click, dblclick, mouseenter, mouseleave, and mousemove.
Common keyboard events include keydown and keyup.
Form events include submit, input, change, focus, and blur.
Use removeEventListener() to remove event listeners.
Events bubble up from the target element to its ancestors by default.
Use event.stopPropagation() to stop event bubbling.
Use event.preventDefault() to prevent default browser behavior.
Event delegation allows you to attach one listener to a parent instead of many listeners to children.
You can create custom events using CustomEvent.
Try It Yourself
Experiment with the code below. Try adding different event types,
modifying the handlers, or creating new event scenarios.
What this code does: Practice example: simulate enter, click, and leave
on a button. Try changing the handlers or adding another event, then click Test My Code.
The comment explains that the code will simulate button interactions rather than attach listeners to a real page element.
clickCount starts at zero and tracks how many times the simulated button-click handler runs.
handleButtonClick increments clickCount each time it is called. The console message includes the updated count, so repeated calls demonstrate how the state changes after every click.
handleMouseEnter logs a message when the pointer enters the button area. It does not change clickCount because entering the area is a different interaction from clicking.
handleMouseLeave logs when the pointer leaves the button area. Like the enter handler, it reports the interaction but does not change the click total.
The commented lines show how the three functions would be attached to click, mouseenter, and mouseleave events on a real button. They are comments so this console demonstration does not need an actual button element.
The console heading introduces the sequence, and the empty log line creates a visual gap before the handler calls.
The blank line and two console messages introduce the interaction sequence. handleMouseEnter simulates the pointer entering the button. The next two calls simulate two clicks, so clickCount increases from zero to one and then two. handleMouseLeave then simulates the pointer leaving the button area. The enter and leave handlers only log messages; only the click handler changes the count.
The blank console output separates the interaction log from the summary. The final console.log prints clickCount, which should be 2 because handleButtonClick was called twice.
Tip:
Event listeners are essential for creating interactive web
applications. Practice using different event types and understand
how events propagate through the DOM to become proficient in
JavaScript.
Understanding event listeners is crucial for building interactive
websites and applications. Mastering events will help you create
responsive user interfaces and handle user interactions
effectively.
🧪 Test Your JavaScript Code
Edit the code on the left and click “Run Code” to see the console output on the right.
📝 JavaScript Code
👁 Console Output
Click “Run Code” to see the output here.
Ctrl + Enterto run codeEscto close
Code Explanation
The complete example stays visible. Use Previous and Next to study each logical block.
Complete code — highlighted block shows the current explanation