addEventListener의 동작 원리와 이벤트 객체, 버블링/캡처링, 이벤트 위임 패턴을 이해한다. 폼 이벤트, 키보드/마우스 이벤트, 사용자 경험을 높이는 이벤트 처리 패턴을 익힌다.
이벤트 — 사용자 행동에 반응하기
이벤트는 브라우저나 사용자가 발생시키는 신호입니다. 클릭, 키 입력, 스크롤, 폼 제출 등이 모두 이벤트입니다. 자바스크립트는 이벤트를 감지하고 그에 맞는 함수(이벤트 핸들러)를 실행합니다.
addEventListener 기본
const button = document.querySelector("#btn");
// 기본 방식
button.addEventListener("click", function(event) {
console.log("클릭됨!", event);
});
// 화살표 함수
button.addEventListener("click", (e) => {
console.log(e.target); // 클릭된 요소
});
// 이벤트 제거 (익명 함수는 제거 불가!)
function handleClick(e) {
console.log("클릭");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick); // 제거 가능
// 한 번만 실행
button.addEventListener("click", handleClick, { once: true });
이벤트 객체
document.addEventListener("click", (e) => {
e.target // 실제 클릭된 요소
e.currentTarget // 이벤트 핸들러가 달린 요소
e.type // "click"
e.clientX, e.clientY // 마우스 좌표 (뷰포트 기준)
e.pageX, e.pageY // 마우스 좌표 (문서 기준)
e.key // 키보드: "Enter", "Escape" 등
e.code // 키보드: "KeyA", "Space" 등
e.ctrlKey // Ctrl 키 눌렸는지
e.shiftKey // Shift 키 눌렸는지
e.altKey // Alt 키 눌렸는지
e.preventDefault() // 기본 동작 방지
e.stopPropagation() // 버블링 중단
});
이벤트 버블링과 캡처링
이벤트는 세 단계로 흐릅니다: 캡처링(위→아래) → 타겟 → 버블링(아래→위).
<!-- HTML 구조 -->
<div id="outer">
<div id="inner">
<button id="btn">클릭</button>
</div>
</div>
// 버블링 (기본): 이벤트가 btn → inner → outer 순으로 전파
document.querySelector("#btn").addEventListener("click", e => {
console.log("btn 클릭");
});
document.querySelector("#inner").addEventListener("click", e => {
console.log("inner도 실행됨"); // 버블링으로 실행!
});
// 버블링 막기
btn.addEventListener("click", e => {
e.stopPropagation();
console.log("btn만 실행됨");
});
// 캡처링 단계에 등록
outer.addEventListener("click", handler, { capture: true }); // 먼저 실행
이벤트 위임 — 핵심 패턴
100개 버튼마다 핸들러를 달지 않고, 부모 하나에 달아서 처리합니다. 동적으로 추가되는 요소에도 자동으로 동작합니다.
// 나쁜 방법: 각 요소마다 핸들러 등록
document.querySelectorAll(".delete-btn").forEach(btn => {
btn.addEventListener("click", deleteItem);
});
// 새로 추가된 버튼에는 동작하지 않음!
// 좋은 방법: 이벤트 위임
document.querySelector("#list").addEventListener("click", (e) => {
// 실제 클릭된 요소 확인
if (e.target.matches(".delete-btn")) {
const id = e.target.dataset.id;
deleteItem(id);
}
if (e.target.matches(".edit-btn")) {
editItem(e.target.dataset.id);
}
});
// closest — 가장 가까운 조상 찾기
document.querySelector("#list").addEventListener("click", (e) => {
const item = e.target.closest(".list-item");
if (!item) return; // 아이템 밖 클릭 무시
const id = item.dataset.id;
handleItemClick(id);
});
폼 이벤트
const form = document.querySelector("#signup-form");
// submit — 폼 제출
form.addEventListener("submit", (e) => {
e.preventDefault(); // 페이지 새로고침 방지!
const data = new FormData(form);
const name = data.get("name");
const email = data.get("email");
submitForm({ name, email });
});
// input — 값이 변할 때마다 (실시간)
const input = document.querySelector("#search");
input.addEventListener("input", (e) => {
search(e.target.value);
});
// change — 입력 완료 후 (포커스 아웃 시)
const select = document.querySelector("#category");
select.addEventListener("change", (e) => {
filterByCategory(e.target.value);
});
// focus / blur
input.addEventListener("focus", () => input.classList.add("focused"));
input.addEventListener("blur", () => input.classList.remove("focused"));
키보드 이벤트
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal();
if (e.key === "Enter" && e.ctrlKey) submitForm();
if (e.key === "s" && (e.ctrlKey || e.metaKey)) {
e.preventDefault(); // 브라우저 저장 방지
saveDocument();
}
});
// 입력 필드에서 Enter 처리
const input = document.querySelector("#input");
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
addItem(input.value);
input.value = "";
}
});
디바운스 — 연속 이벤트 최적화
// 검색창에 타이핑할 때마다 API 호출하면 너무 많은 요청 발생
// 디바운스: 마지막 입력 후 일정 시간 지나면 한 번만 실행
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const handleSearch = debounce((value) => {
fetchSearchResults(value);
}, 300);
input.addEventListener("input", (e) => handleSearch(e.target.value));
// 타이핑을 멈춘 뒤 300ms 후에 한 번만 API 호출
관련 검색어 · addEventListener 사용법, 이벤트 위임 패턴, 이벤트 버블링, preventDefault, 폼 이벤트, 디바운스 구현