DOM event contract

이벤트는 대상 요소에서 끝나지 않고 전파 경로를 가진다

이벤트 처리는 요소를 찾고 함수를 붙이는 작업에서 끝나지 않습니다. 어떤 단계에서 실행할지, 기본 동작을 막을지, 상위 요소로 전파할지, 리스너를 언제 제거할지까지 계약으로 고정해야 화면 동작이 흔들리지 않습니다.

경로 계산 캡처 대상 실행 버블 위임 기본 동작
target 가장 안쪽 시작점이고, 위임에서는 실제 버튼을 다시 찾아야 한다.
phase 같은 listener라도 캡처·대상·버블 중 어디서 실행되는지 다르다.
control 전파 차단과 기본 동작 차단은 서로 다른 계약으로 분리한다.
1 path

전파 경로 계산

브라우저가 window, document, 조상 요소, target까지의 경로를 정합니다.

2 capture

캡처 단계

{ capture: true } 리스너가 바깥에서 안쪽으로 실행됩니다.

3 target

대상 단계

실제 클릭·입력 요소의 listener가 실행되고 상태를 읽거나 바꿉니다.

4 bubble

버블 단계

대부분 이벤트는 조상으로 올라가므로 위임 패턴을 만들 수 있습니다.

5 default

기본 동작

preventDefault()가 없으면 링크 이동이나 폼 제출이 이어집니다.

delegation

동적 목록은 부모에 리스너를 붙인다

list.addEventListener('click', (event) => {
  const button = event.target.closest('button[data-id]');
  if (!button || !list.contains(button)) return;
  removeItem(button.dataset.id);
});

버튼이 나중에 추가되어도 부모 리스너는 유지됩니다. 단, target만 믿지 말고 closest()contains()로 처리 범위를 확인합니다.

전파 제어 API를 섞어 쓰는 기준

API 막는 대상 대표 상황
preventDefault 브라우저 기본 동작 검증 실패 시 폼 제출 중단
stopPropagation 상위 요소로의 전파 모달 내부 클릭이 배경 닫기를 트리거하지 않게 함
stopImmediatePropagation 같은 요소의 뒤 listener까지 중단 동일 요소에 여러 handler가 붙은 충돌 상황
removeEventListener 같은 함수 참조의 listener 페이지 이탈, 일회성 처리, AbortSignal 정리
capture

캡처 단계에서 실행

문서 전체 감시나 바깥 클릭 판정처럼 먼저 보고 싶은 처리에 사용합니다.

once

한 번 실행 후 제거

초기 안내 닫기, 최초 상호작용 기록처럼 반복 실행이 필요 없을 때 씁니다.

passive

스크롤 기본 동작 허용

touch, wheel에서 preventDefault()를 호출하지 않겠다는 약속입니다.

signal

AbortController로 정리

여러 listener를 한 번에 해제해야 할 때 같은 signal을 공유합니다.

reference

제거에는 같은 함수 필요

익명 함수를 다시 만들면 removeEventListener()가 제거하지 못합니다.

대상 선택

querySelector는 첫 요소, querySelectorAll은 정적 NodeList임을 구분합니다.

상태 변경

사용자 입력은 DOM 속성, 화면 텍스트, CSS 클래스 중 어디를 바꿀지 먼저 정합니다.

수명 관리

동적 요소 제거, 라우트 이동, 모달 닫힘 시 listener와 타이머 정리 지점을 남깁니다.