📘 Lesson  ·  Lesson 37

Event Listeners

Event Listeners

addEventListener — the standard way

let btn = document.querySelector("#myBtn");

btn.addEventListener("click", function() {
    console.log("Clicked!");
});

// With an arrow function (very common):
btn.addEventListener("click", () => console.log("Clicked!"));

// With a named function — note: NO parentheses!
function handleClick() { console.log("Clicked!"); }
btn.addEventListener("click", handleClick);      // ✓ pass the function
btn.addEventListener("click", handleClick());    // ✗ calls it immediately!
addEventListener(eventName, function) attaches a function to run whenever that event happens on that element. Two things to get right. First, the event name is a plain string WITHOUT "on" — it's "click", not "onclick". Second, and this catches everyone: you pass the function itself, not a call to it. Writing handleClick() with parentheses runs the function immediately and passes its return value — a classic bug. Leave the parentheses off.

Why Not onclick?

// ✗ The old way — only ONE handler survives
btn.onclick = function() { console.log("First"); };
btn.onclick = function() { console.log("Second"); };   // overwrites the first!
// Output on click: "Second" only

// ✓ addEventListener — as many handlers as you like
btn.addEventListener("click", () => console.log("First"));
btn.addEventListener("click", () => console.log("Second"));
// Output on click: "First" then "Second"

// ✗✗ Worst of all — inline HTML:
<button onclick="doSomething()">Click</button>   // mixes JS into HTML
addEventListener is preferred because you can attach MULTIPLE handlers to the same event. The older element.onclick = fn property holds only one function — assigning a second silently replaces the first. Worse still is putting onclick="..." directly in your HTML: it mixes behaviour into structure, exactly the separation-of-concerns problem you avoided with external CSS and JS files. Always use addEventListener. It also gives you options like capturing and once, and can be cleanly removed.

removeEventListener and once

function handleClick() { console.log("Hi"); }

btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);   // ✓ works — same reference

// ✗ This does NOT work — different function objects:
btn.addEventListener("click", () => console.log("Hi"));
btn.removeEventListener("click", () => console.log("Hi"));   // no effect

// Run a listener only once:
btn.addEventListener("click", handleClick, { once: true });
To remove a listener you must pass the exact same function reference you added. This is why anonymous and arrow functions can't be removed — each one you write creates a brand-new function object, so removeEventListener has nothing to match. If you'll ever need to remove a listener, give the function a name. For the common "only fire once" case (a welcome popup, a one-time animation), the { once: true } option handles it automatically and cleans up after itself.

Event Bubbling — events travel upward

<div id="parent">
    <button id="child">Click me</button>
</div>

parent.addEventListener("click", () => console.log("Parent"));
child.addEventListener("click", () => console.log("Child"));

// Clicking the button prints:  "Child"  then  "Parent"
// The event BUBBLES up from the clicked element to its ancestors.

// To stop it:
child.addEventListener("click", (e) => {
    e.stopPropagation();     // parent's handler won't run
});
When you click an element, the event fires on that element and then "bubbles" upward through every ancestor — child, then parent, then grandparent, all the way to document. This explains a puzzling behaviour: clicking a button inside a div triggers the div's click handler too. Usually this is helpful (it's what makes delegation possible), but sometimes you need it to stop — e.stopPropagation() halts the bubbling right there. There's also a "capturing" phase that travels downward first, rarely used in practice.

Event Delegation — one listener for many elements

<ul id="todoList">
    <li>Task 1 <button class="del">X</button></li>
    <li>Task 2 <button class="del">X</button></li>
</ul>

// ✗ Adding a listener to each button — breaks for NEW items added later
document.querySelectorAll(".del").forEach(b => b.addEventListener(...));

// ✓ ONE listener on the parent, using bubbling + e.target:
document.querySelector("#todoList").addEventListener("click", (e) => {
    if (e.target.classList.contains("del")) {
        e.target.closest("li").remove();     // delete that task
    }
});
Event delegation puts a single listener on a parent and uses bubbling plus e.target to work out which child was clicked. It solves two problems at once: it's far more efficient than hundreds of individual listeners, and — crucially — it works for elements added to the page LATER, since the parent was listening all along. Attaching listeners directly to items fails for dynamically created ones. This pattern combines everything from these DOM chapters: bubbling, e.target, classList.contains, closest(), and remove(). It's exactly how the to-do project at the end of this course will work.

Exam Corner

Q: Write the addEventListener syntax. element.addEventListener("click", handlerFunction)

Q: Why is addEventListener better than onclick? It allows multiple handlers on the same event and keeps JS out of HTML.

Q: Why can't you remove an arrow-function listener? Because removeEventListener needs the exact same function reference.

Q: What is event bubbling? An event fires on the target then travels up through its ancestor elements.

Q: Why use event delegation? One listener handles many children, including elements added later.

addEventListener — standard तरीका

let btn = document.querySelector("#myBtn");

btn.addEventListener("click", function() {
    console.log("Clicked!");
});

// Arrow function ke saath (bahut common):
btn.addEventListener("click", () => console.log("Clicked!"));

// Named function ke saath — note: parentheses NAHI!
function handleClick() { console.log("Clicked!"); }
btn.addEventListener("click", handleClick);      // ✓ function pass karo
btn.addEventListener("click", handleClick());    // ✗ turant call kar deta!
addEventListener(eventName, function) ऐसा function जोड़ता है जो उस element पर वह event होने पर चले. दो चीज़ें सही कीजिए. पहली, event नाम "on" के BINA सादी string है — यह "click" है, "onclick" नहीं. दूसरी, और यह सबको फंसाती है: आप function खुद भेजते हैं, उसका call नहीं. handleClick() parentheses के साथ लिखना function तुरंत चलाकर उसकी return value भेजता है — classic bug. Parentheses छोड़ दीजिए.

onclick क्यों नहीं?

// ✗ Purana tarika — sirf EK handler bachta hai
btn.onclick = function() { console.log("First"); };
btn.onclick = function() { console.log("Second"); };   // pehle ko overwrite!
// Click par output: sirf "Second"

// ✓ addEventListener — jitne chahe handlers
btn.addEventListener("click", () => console.log("First"));
btn.addEventListener("click", () => console.log("Second"));
// Click par output: "First" phir "Second"

// ✗✗ Sabse bura — inline HTML:
<button onclick="doSomething()">Click</button>   // JS ko HTML me milata
addEventListener इसलिए पसंद है क्योंकि आप एक ही event पर KAI handlers लगा सकते हैं. पुरानी element.onclick = fn property सिर्फ एक function रखती है — दूसरा assign करना चुपचाप पहले की जगह ले लेता है. उससे भी बुरा है अपने HTML में सीधे onclick="..." रखना: यह behaviour को structure में मिलाता है, ठीक वही separation-of-concerns समस्या जो आपने external CSS और JS files से टाली. हमेशा addEventListener use कीजिए. यह capturing और once जैसे options भी देता है, और साफ-साफ हटाया जा सकता है.

removeEventListener और once

function handleClick() { console.log("Hi"); }

btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick);   // ✓ chalta — vahi reference

// ✗ Yeh NAHI chalta — alag function objects:
btn.addEventListener("click", () => console.log("Hi"));
btn.removeEventListener("click", () => console.log("Hi"));   // koi asar nahi

// Listener sirf ek baar chalaao:
btn.addEventListener("click", handleClick, { once: true });
Listener हटाने को आपको ठीक वही function reference भेजनी होगी जो आपने जोड़ी थी. इसीलिए anonymous और arrow functions हटाए नहीं जा सकते — आप जो भी लिखें वह बिल्कुल नया function object बनाता है, तो removeEventListener के पास match करने को कुछ नहीं. अगर कभी listener हटाना हो, function को नाम दीजिए. आम "सिर्फ एक बार चले" मामले के लिए (welcome popup, एक बार की animation), { once: true } option इसे अपने आप संभालता है और खुद साफ हो जाता है.

Event Bubbling — events ऊपर की ओर जाते हैं

<div id="parent">
    <button id="child">Click me</button>
</div>

parent.addEventListener("click", () => console.log("Parent"));
child.addEventListener("click", () => console.log("Child"));

// Button click karne par print hota hai:  "Child"  phir  "Parent"
// Event click kiye element se uske ancestors tak BUBBLE karta hai.

// Rokne ko:
child.addEventListener("click", (e) => {
    e.stopPropagation();     // parent ka handler nahi chalega
});
जब आप element click करें, event उस element पर fire होकर फिर हर ancestor से "bubble" करता ऊपर जाता है — child, फिर parent, फिर grandparent, document तक. यह एक उलझाने वाला व्यवहार समझाता है: div के अंदर button click करना div का click handler भी trigger करता है. आमतौर पर यह मददगार है (यही delegation संभव करता है), पर कभी आपको इसे रोकना पड़ता है — e.stopPropagation() bubbling वहीं रोक देता है. एक "capturing" phase भी है जो पहले नीचे की ओर जाता है, व्यवहार में शायद ही use होता.

Event Delegation — कई elements के लिए एक listener

<ul id="todoList">
    <li>Task 1 <button class="del">X</button></li>
    <li>Task 2 <button class="del">X</button></li>
</ul>

// ✗ Har button par listener — BAAD me jode NAYE items ke liye tootta hai
document.querySelectorAll(".del").forEach(b => b.addEventListener(...));

// ✓ Parent par EK listener, bubbling + e.target use karte:
document.querySelector("#todoList").addEventListener("click", (e) => {
    if (e.target.classList.contains("del")) {
        e.target.closest("li").remove();     // vah task delete karo
    }
});
Event delegation parent पर एक listener रखता है और bubbling तथा e.target से पता लगाता है कौन-सा child click हुआ. यह एक साथ दो समस्याएं हल करता है: यह सैकड़ों अलग listeners से कहीं ज़्यादा efficient है, और — अहम बात — यह BAAD में page में जोड़े elements के लिए भी चलता है, चूंकि parent पूरे समय सुन रहा था. Items पर सीधे listeners लगाना dynamically बने items के लिए fail होता है. यह pattern इन DOM chapters का सब कुछ मिलाता है: bubbling, e.target, classList.contains, closest(), और remove(). इस course के अंत का to-do project ठीक ऐसे ही चलेगा.

Exam Corner

Q: addEventListener syntax लिखिए. element.addEventListener("click", handlerFunction)

Q: addEventListener onclick से बेहतर क्यों है? यह एक ही event पर कई handlers की अनुमति देता है और JS को HTML से बाहर रखता है.

Q: Arrow-function listener क्यों नहीं हटा सकते? क्योंकि removeEventListener को ठीक वही function reference चाहिए.

Q: Event bubbling क्या है? Event target पर fire होकर फिर उसके ancestor elements से ऊपर जाता है.

Q: Event delegation क्यों use करें? एक listener कई children संभालता है, बाद में जोड़े elements सहित.
← Back to JavaScript Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

💻 Live Code Editor

इस पेज का code यहाँ तैयार है — बदलिए और तुरंत नतीजा देखिए. कुछ install किए बिना.
👁 Live Preview
सब कुछ आपके browser में ही चलता है — कोई server, कोई signup नहीं. JavaScript का console.log देखने के लिए F12 दबाइए.