핵심 요약
addEventListener는 그 순간 존재하는 요소에만 붙는다. 그래서 나중에 동적으로 추가한 요소는 이벤트가 안 먹는다. 해결책은 이벤트 위임(event delegation) — 변하지 않는 부모에 리스너를 하나 걸고, 이벤트 버블링을 이용해 실제 클릭된 자식을 event.target으로 판별한다.
1. 왜 안 먹나
// 페이지 로드 시점의 버튼에만 붙음
document.querySelectorAll('.btn')
.forEach(b => b.addEventListener('click', handler))
// 이후 추가된 버튼엔 리스너가 없음
list.innerHTML += '<button class="btn">새 버튼</button>'
// → 이 버튼 클릭은 반응 없음
2. 이벤트 위임으로 해결
// 부모(안 변하는 컨테이너)에 한 번만
list.addEventListener('click', (e) => {
const btn = e.target.closest('.btn')
if (!btn) return // .btn이 아니면 무시
handler(btn) // 실제 클릭된 버튼 처리
})
closest('.btn')는 클릭된 지점에서 위로 올라가며 .btn을 찾아, 버튼 안의 아이콘·텍스트를 클릭해도 정확히 잡는다.
3. 왜 되나 — 버블링
자식에서 발생한 이벤트는 부모로 거슬러 올라간다(버블링). 부모의 리스너가 이 올라온 이벤트를 받고, event.target으로 최초 발생 요소를 알 수 있다. 그래서 자식이 나중에 추가돼도 부모 리스너가 다 처리한다.
4. 장점
| 항목 | 개별 바인딩 | 위임 |
|---|---|---|
| 동적 요소 | 안 먹음 | 자동 처리 |
| 리스너 수 | 요소마다 1개 | 부모에 1개 |
| 메모리 | 많음 | 적음 |
5. 함정
e.target은 실제 클릭 지점(자식 아이콘일 수 있음) — 원하는 요소는closest()로 찾는다focus·blur는 버블링을 안 한다 — 위임하려면focusin·focusout을 쓴다- React·Vue 등은 내부적으로 이미 위임을 쓰므로 프레임워크에선 그냥
onClick을 쓰면 된다
자주 묻는 질문
e.target과 e.currentTarget은 뭐가 다른가요?
e.target은 이벤트가 실제로 발생한 요소(클릭된 자식), e.currentTarget은 리스너가 붙어 있는 요소(부모)입니다. 위임에서 "누가 눌렸나"는 target, "어디에 걸었나"는 currentTarget으로 구분합니다.
모든 이벤트를 위임할 수 있나요?
버블링하는 이벤트는 됩니다(click·input·keydown 등). focus·blur·mouseenter처럼 버블링하지 않는 이벤트는 안 되며, 각각 focusin·focusout·mouseover 같은 버블링 대체 이벤트를 써야 합니다.
React에서도 위임을 직접 해야 하나요?
아니요. React는 내부적으로 루트에 이벤트를 위임해 처리하므로 그냥 요소에 onClick을 붙이면 됩니다. 동적으로 렌더된 요소도 자동으로 동작하니 위임을 수동으로 구현할 필요가 없습니다.

댓글 0