Skip to Content

이벤트

이벤트 리스너 (addEventListener, removeEventListener)

addEventListener — 이벤트 등록

const btn = document.querySelector("#btn"); btn.addEventListener("click", () => { console.log("클릭됨"); });

같은 요소에 여러 개의 리스너를 등록할 수도 있다.

btn.addEventListener("click", () => console.log("첫 번째")); btn.addEventListener("click", () => console.log("두 번째")); // 클릭하면 둘 다 실행됨

removeEventListener — 이벤트 제거

등록할 때 사용한 것과 **동일한 함수(익명 함수 X)**를 넘겨야 제거된다.

function handleClick() { console.log("클릭됨"); } btn.addEventListener("click", handleClick); btn.removeEventListener("click", handleClick); // 정상 제거 btn.addEventListener("click", () => console.log("클릭")); btn.removeEventListener("click", () => console.log("클릭")); // 제거 안 됨 (다른 함수로 취급)

옵션 — once, capture

btn.addEventListener("click", handleClick, { once: true }); // 한 번만 실행되고 자동 제거 btn.addEventListener("click", handleClick, { capture: true }); // 캡처링 단계에서 실행(`capture`는 이벤트 전파 방식과 관련된 옵션)


이벤트 객체 (event.target, preventDefault, stopPropagation)

이벤트 콜백의 매개변수로 전달되는 객체에는 이벤트에 대한 다양한 정보가 담겨있다.

event.target — 실제로 이벤트가 발생한 요소

document.querySelector("#list").addEventListener("click", (event) => { console.log(event.target); // 클릭된 실제 요소 console.log(event.target.textContent); });

preventDefault — 기본 동작 막기

const form = document.querySelector("form"); form.addEventListener("submit", (event) => { event.preventDefault(); // 폼 제출 시 페이지가 새로고침되는 기본 동작을 막음 console.log("폼 제출 처리"); });
const link = document.querySelector("a"); link.addEventListener("click", (event) => { event.preventDefault(); // 링크 이동 막기 });

stopPropagation — 이벤트 전파 막기

child.addEventListener("click", (event) => { event.stopPropagation(); // 이 이벤트가 부모로 전파되는 것을 막음 console.log("child만 실행됨"); });


버블링과 캡처링 (이벤트 전파 방식)

하나의 요소에서 이벤트가 발생하면, 그 이벤트는 DOM 트리를 따라 순서대로 전파된다.

<div id="outer"> <div id="inner"> <button id="btn">클릭</button> </div> </div>

버블링 (Bubbling) — 기본 동작, 안쪽에서 바깥쪽으로

outer.addEventListener("click", () => console.log("outer")); inner.addEventListener("click", () => console.log("inner")); btn.addEventListener("click", () => console.log("btn")); // btn 클릭 시 실행 순서: btn → inner → outer

이벤트가 발생한 요소에서 시작해서 부모 방향으로 계속 전파된다.

캡처링 (Capturing) — 바깥쪽에서 안쪽으로

addEventListener의 세 번째 인자를 true로 주면 캡처링 단계에서 실행된다.

outer.addEventListener("click", () => console.log("outer"), { capture: true }); inner.addEventListener("click", () => console.log("inner"), { capture: true }); btn.addEventListener("click", () => console.log("btn"), { capture: true }); // btn 클릭 시 실행 순서: outer → inner → btn

JS 이벤트



이벤트 위임 (Event Delegation)

자식 요소마다 각각 이벤트 리스너를 등록하는 대신, 부모 요소 하나에만 리스너를 등록하고 버블링을 이용해 처리하는 방식이다.

문제 상황 — 항목마다 리스너를 등록하면

const items = document.querySelectorAll(".item"); items.forEach((item) => { item.addEventListener("click", () => console.log(item.textContent)); }); // 나중에 새로 추가되는 .item에는 리스너가 자동으로 붙지 않음

이벤트 위임으로 개선

const list = document.querySelector("#list"); list.addEventListener("click", (event) => { if (event.target.classList.contains("item")) { console.log(event.target.textContent); } });

list에만 리스너를 하나 등록해두면, 이후에 .item이 새로 추가되더라도 버블링을 통해 list까지 이벤트가 전달되기 때문에 별도로 리스너를 다시 붙일 필요가 없다.

실전 예시 — 동적으로 추가되는 삭제 버튼 처리

const list = document.querySelector("#itemList"); list.addEventListener("click", (event) => { if (event.target.matches(".delete-btn")) { event.target.closest("li").remove(); } }); function addItem(name) { list.insertAdjacentHTML( "beforeend", `<li>${name} <button class="delete-btn">삭제</button></li>` ); } addItem("gori");

새로 추가되는 삭제 버튼마다 리스너를 따로 등록하지 않아도, 부모(list)의 리스너 하나가 모든 클릭을 처리한다. 메모리 사용량도 줄고 코드도 간결해져서 실무에서 자주 쓰이는 패턴이다.



마지막 수정