Practice Questions
1. What is the difference between event.target and event.currentTarget? Illustrate with an example.
Solution
event.target is the element where the event originated (the element the user actually interacted with). event.currentTarget is the element that the event listener is attached to.
// HTML: <div id="parent"><button id="child">Click</button></div>
const parent = document.querySelector("#parent");
parent.addEventListener("click", (event) => {
console.log(event.target); // <button> (where click originated)
console.log(event.currentTarget); // <div> (where listener is attached)
});
They are the same only when the listener is on the exact element that was clicked.
2. Write code that listens for a form submission, prevents the page from reloading, and logs the value of an input with the name "email".
Solution
const form = document.querySelector("#myForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
const formData = new FormData(event.target);
const email = formData.get("email");
console.log(email);
});
event.preventDefault() stops the browser’s default behavior of reloading the page on form submission.
3. Why does the following call to removeEventListener fail to remove the handler? How would you fix it?
button.addEventListener("click", () => console.log("Clicked"));
button.removeEventListener("click", () => console.log("Clicked"));
Solution
It fails because the two arrow functions are different function objects, even though they have the same body. removeEventListener requires the exact same function reference that was passed to addEventListener.
Fix it by using a named function reference:
function handleClick() {
console.log("Clicked");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);
4. Describe the three phases of event propagation. In which phase do event listeners run by default?
Solution
The three phases are:
- Capturing phase: The event travels down from the window through the DOM tree to the target element.
- Target phase: The event reaches the element where it originated.
- Bubbling phase: The event travels back up from the target to the window.
By default, event listeners run during the bubbling phase. To listen during the capturing phase, pass true or { capture: true } as the third argument to addEventListener.
5. The following code attaches a click listener to every delete button in a list. Rewrite it using event delegation so that only one listener is needed. The rewritten code should also handle delete buttons added to the list after page load.
document.querySelectorAll(".delete-btn").forEach((btn) => {
btn.addEventListener("click", (event) => {
event.target.closest("li").remove();
});
});
Solution
const list = document.querySelector("#item-list");
list.addEventListener("click", (event) => {
if (event.target.matches(".delete-btn")) {
event.target.closest("li").remove();
}
});
A single listener on the parent uses event.target.matches() to check which child was clicked. Because the listener is on the container, it automatically works for elements added dynamically.
6. What is the difference between event.key and event.code for keyboard events? Write code that detects when the user presses Ctrl+S and prevents the browser’s default save behavior.
Solution
event.key is the character produced by the key press (affected by keyboard layout and modifier keys, e.g., "a" or "A"). event.code is the physical key on the keyboard (consistent across layouts, e.g., "KeyA").
document.addEventListener("keydown", (event) => {
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
saveDocument();
}
});
7. Write code that creates a custom event called "app:themeChanged" with a detail containing a theme property set to "dark". Dispatch the event on document and write a listener that logs the new theme.
Solution
// Listener
document.addEventListener("app:themeChanged", (event) => {
console.log("Theme changed to:", event.detail.theme);
});
// Create and dispatch
const event = new CustomEvent("app:themeChanged", {
detail: { theme: "dark" },
});
document.dispatchEvent(event);
The detail property is how you attach data to a custom event. The namespace prefix app: avoids conflicts with native events.
8. Using Node.js EventEmitter, write a Counter class that emits a "changed" event (with the new count) whenever increment() or decrement() is called. Show how to use it.
Solution
import { EventEmitter } from "events";
class Counter extends EventEmitter {
constructor() {
super();
this.count = 0;
}
increment() {
this.count++;
this.emit("changed", this.count);
}
decrement() {
this.count--;
this.emit("changed", this.count);
}
}
// Usage
const counter = new Counter();
counter.on("changed", (count) => {
console.log("Count is now:", count);
});
counter.increment(); // Count is now: 1
counter.increment(); // Count is now: 2
counter.decrement(); // Count is now: 1
9. What does stopPropagation() do? How is it different from stopImmediatePropagation()?
Solution
stopPropagation() prevents the event from continuing to propagate to parent (or child) elements, but other listeners on the same element still run.
stopImmediatePropagation() does the same and also prevents other listeners on the same element from running.
child.addEventListener("click", (event) => {
event.stopImmediatePropagation();
console.log("First handler"); // Runs
});
child.addEventListener("click", () => {
console.log("Second handler"); // Never runs
});
parent.addEventListener("click", () => {
console.log("Parent handler"); // Never runs
});
10. Fill in the blanks in the following table comparing browser events and Node.js events:
| Action | Browser (DOM) | Node.js (EventEmitter) |
|---|---|---|
| Add listener | ___________ |
on |
| Remove listener | ___________ |
off |
| Trigger event | ___________ |
___________ |
| One-time listener | ___________ |
once |
| Pass data | ___________ |
___________ |
Solution
| Action | Browser (DOM) | Node.js (EventEmitter) |
|---|---|---|
| Add listener | addEventListener |
on |
| Remove listener | removeEventListener |
off |
| Trigger event | dispatchEvent |
emit |
| One-time listener | { once: true } option |
once |
| Pass data | CustomEvent({ detail }) |
Arguments to emit |