LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는 target에서 끝나지 않는다

2026년 7월 7일·5분 읽기
오늘의 질문: “버튼에 클릭 핸들러를 달았으니 이 이벤트는 버튼에서만 처리된다고 봐도 될까?”

먼저 보는 코드

js
const list = document.querySelector(".todo-list");

list.addEventListener("click", (event) => {
  if (event.target.matches("button[data-delete]")) {
    deleteTodo(event.target.dataset.id);
  }
});

삭제 버튼마다 핸들러를 붙인 코드가 아닙니다. 리스트 하나에 핸들러를 붙여 두고, 클릭 이벤트가 위로 올라오는 흐름을 이용합니다. 이것이 event delegation입니다. 새 항목이 나중에 추가되어도 리스트의 핸들러 하나가 계속 처리할 수 있습니다.

DOM 이벤트 전파 경로

틀리기 쉬운 직감: 이벤트는 target에서만 일어난다

브라우저 이벤트는 보통 세 단계를 거칩니다.

단계방향코드 리뷰에서 볼 질문
capture바깥 → target부모가 먼저 가로채야 하는 이유가 있는가?
target실제 이벤트 발생 요소event.target과 현재 핸들러 위치가 같은가?
bubbletarget → 바깥delegation이나 부모 공통 처리를 의도했는가?

기본 addEventListener("click", handler)는 bubble 단계에서 동작합니다. capture 단계에서 듣고 싶다면 세 번째 인자나 options를 써야 합니다.

js
document.addEventListener("click", logBeforeTarget, { capture: true });
document.addEventListener("click", logAfterTarget);

이 차이를 모르면 “왜 부모 핸들러가 먼저 실행되지?” 또는 “왜 modal 바깥 클릭이 버튼 클릭까지 같이 닫아 버리지?” 같은 버그를 잘못 읽습니다.

target과 currentTarget은 다르다

이벤트 핸들러에서 자주 섞이는 값이 target과 currentTarget입니다.

js
list.addEventListener("click", (event) => {
  console.log(event.target); // 실제 클릭된 가장 안쪽 요소
  console.log(event.currentTarget); // 핸들러가 붙어 있는 list
});

버튼 안에 아이콘 <span>이 있으면 event.target은 버튼이 아니라 span일 수 있습니다. 그래서 delegation 코드는 대개 closest로 의도를 조금 더 안전하게 표현합니다.

js
list.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-delete]");
  if (!button || !list.contains(button)) return;

  deleteTodo(button.dataset.id);
});

리뷰 질문은 간단합니다.

이 코드는 실제 클릭된 노드가 바뀌어도 의도한 요소를 찾을 수 있는가?

stopPropagation은 마지막 수단에 가깝다

event.stopPropagation()은 이벤트가 더 위로 올라가지 못하게 합니다. modal, dropdown, nested clickable card 같은 곳에서 필요할 수 있습니다. 하지만 습관적으로 넣으면 부모의 접근성 처리, analytics, delegation 핸들러를 조용히 끊을 수 있습니다.

js
button.addEventListener("click", (event) => {
  event.stopPropagation();
  openMenu();
});

리뷰에서는 “정말 전파를 막아야 하는가?”를 봅니다. 단순히 부모 핸들러가 귀찮아서 막는다면 구조를 다시 잡는 편이 낫습니다. 예를 들어 부모가 클릭 위치를 더 명확히 검사하거나, 버튼과 카드의 역할을 분리할 수 있습니다.

preventDefault는 기본 동작을 막는다

preventDefault()는 전파를 막는 함수가 아닙니다. 링크 이동, 폼 제출, 체크박스 토글 같은 브라우저의 기본 동작을 막습니다.

js
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  await saveForm(new FormData(form));
});

프론트엔드에서 이 둘을 섞으면 버그가 생깁니다.

원하는 일맞는 도구
폼의 새로고침 제출 막기preventDefault()
부모 클릭 핸들러로 안 올리기stopPropagation()
둘 다 필요한지 판단하기의도를 별도로 설명하기

React synthetic event는 브라우저 이벤트를 감싼다

React에서 onClick은 브라우저의 native event를 그대로 노출하기보다 React의 synthetic event 시스템을 거칩니다. 최신 React에서는 예전처럼 event pooling 때문에 값을 비동기에서 못 읽는 문제는 크게 줄었지만, 리뷰 포인트는 여전히 남습니다.

tsx
function Card({ onOpen }: { onOpen: () => void }) {
  return (
    <article onClick={onOpen}>
      <button
        onClick={(event) => {
          event.stopPropagation();
          bookmark();
        }}
      >
        저장
      </button>
    </article>
  );
}

여기서 버튼 클릭이 카드 열기까지 이어지면 UX가 깨집니다. 반대로 모든 내부 버튼에 stopPropagation을 흩뿌리면 나중에 analytics나 키보드 접근성 흐름이 끊길 수 있습니다. React 코드에서도 결국 질문은 같습니다.

부모 클릭 영역이 너무 넓지 않은가? 버튼/링크/카드의 역할이 겹치지 않는가? 이벤트를 막는 코드가 local fix인지, 구조 문제를 숨기는지 설명되는가?

프론트엔드 코드 리뷰 체크리스트

delegation을 쓴다면 target, closest, contains 검사가 충분한가? capture가 필요한 코드인지, 기본 bubble 단계로 충분한지 설명되는가? stopPropagation이 부모의 공통 처리나 analytics를 끊지 않는가? preventDefault와 stopPropagation을 목적에 맞게 구분했는가? React에서 clickable card 안에 button/link를 중첩해 역할 충돌을 만들지 않았는가? 키보드 접근성과 기본 브라우저 동작을 불필요하게 막지 않았는가?

이벤트 코드는 “핸들러가 어디 붙었는가”만 보면 부족합니다. 실제 클릭된 노드, 현재 핸들러 위치, 전파 단계, 기본 동작, 프레임워크의 이벤트 레이어까지 이어서 읽어야 합니다.

Day 12 복습 퀴즈

Quiz1 / 4
Q.event.target과 event.currentTarget의 차이로 가장 알맞은 것은 무엇일까요?

Post Q&A

오케이징에게 물어보기

자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는 target에서 끝나지 않는다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/javascript-quizbook
파일 12개, 폴더 0개
자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을 수 없을까자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네 가지 방법자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와 ToPrimitive 흐름자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티 descriptor와 freeze의 경계자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사, Map/Set의 메모리 감각자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된 값자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와 Promise 타이밍자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이 정한다자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도 프로토타입을 읽는다자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는 target에서 끝나지 않는다

같은 섹션의 대표 이미지

12 posts · latest first
DOM 이벤트가 capture 단계로 내려가고 bubble 단계로 올라가는 경로 다이어그램
Study26. 07. 08.

자바스크립트 퀴즈북 리마인드 Day 12: 이벤트는.

DOM 이벤트의 capture/bubble, delegation, default action, React synthetic event 경계를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 08.SEOJing
인스턴스에서 프로토타입과 Object.prototype으로 이어지는 프로퍼티 조회 경로 다이어그램
Study26. 07. 07.

자바스크립트 퀴즈북 리마인드 Day 11: class를 봐도.

프로토타입 조회, class 문법, 인스턴스 필드와 prototype 메서드 차이를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 07. 07.SEOJing
입력, 순수 계산, 출력, 부수 효과 경계를 나누어 함수 설계를 보여주는 다이어그램
Study26. 07. 06.

자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다.

고차 함수, 순수 함수, 부수 효과, async 함수의 Promise 반환을 코드 리뷰에서 어떻게 읽을지 정리합니다.

26. 07. 06.SEOJing
메서드 호출, 분리된 함수, 이벤트 콜백, bind와 arrow function의 this 차이를 보여주는 다이어그램
Study26. 07. 05.

자바스크립트 퀴즈북 리마인드 Day 9: this는 호출한 쪽이.

this 바인딩을 정의 위치가 아니라 호출 형태, 메서드 분리, arrow function, bind 기준으로 읽는 방법을 코드 리뷰 관점에서 정리합니다.

26. 07. 05.SEOJing
콜 스택이 비워진 뒤 마이크로태스크 큐가 먼저 실행되고 다음 태스크로 넘어가는 이벤트 루프 다이어그램
Study26. 07. 04.

자바스크립트 퀴즈북 리마인드 Day 8: 이벤트 루프와.

콜 스택, 태스크 큐, 마이크로태스크 큐를 기준으로 Promise와 setTimeout 실행 순서를 코드 리뷰 관점에서 정리합니다.

26. 07. 04.SEOJing
클로저가 생성 위치의 바인딩을 기억해 나중 실행에서 오래된 값을 읽을 수 있음을 보여주는 다이어그램
Study26. 06. 29.

자바스크립트 퀴즈북 리마인드 Day 7: 클로저 버그와 오래된.

클로저가 값 복사가 아니라 렉시컬 환경 참조라는 점을 stale closure, 루프 콜백, React 이벤트 리뷰 관점에서 정리합니다.

26. 06. 29.SEOJing
스코프 체인과 호이스팅 관계를 단순화한 다이어그램
Study26. 06. 28.

자바스크립트 퀴즈북 리마인드 Day 6: 스코프 체인과 호이스팅.

스코프 체인, 클로저, var/let/const 호이스팅 차이를 콜백·상태 버그 리뷰 관점에서 짧게 정리합니다.

26. 06. 28.SEOJing
JavaScript 객체 참조와 Map, Set, WeakMap의 참조 구조를 비교한 다이어그램
Study26. 06. 27.

자바스크립트 퀴즈북 리마인드 Day 5: 참조, 복사,.

객체 참조와 얕은 복사, Object와 Map/Set의 차이, WeakMap/WeakSet이 필요한 메모리 상황을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 27.SEOJing
JavaScript 프로퍼티 descriptor가 data descriptor와 accessor descriptor로 갈라지는 구조 다이어그램
Study26. 06. 26.

자바스크립트 퀴즈북 리마인드 Day 4: 프로퍼티.

객체 프로퍼티를 key/value가 아니라 descriptor로 읽는 법, getter/setter와 defineProperty, preventExtensions/seal/freeze의 경계를 코드 리뷰 관점에서 정리합니다.

26. 06. 26.SEOJing
객체가 ToPrimitive를 거쳐 + 연산자의 문자열 연결 또는 숫자 덧셈으로 갈라지는 흐름 다이어그램
Study26. 06. 25.

자바스크립트 퀴즈북 리마인드 Day 3: + 연산자와.

객체가 원시값으로 바뀌는 ToPrimitive 흐름, + 연산자의 문자열 연결과 숫자 덧셈 분기, Symbol.toPrimitive가 코드 리뷰에서 왜 중요한지 정리합니다.

26. 06. 25.SEOJing
==, ===, Object.is, SameValueZero의 차이를 정리한 JavaScript equality 다이어그램
Study26. 06. 24.

자바스크립트 퀴즈북 리마인드 Day 2: 같은 값인지 묻는 네.

==, ===, Object.is, SameValueZero가 각각 어떤 비교 알고리즘을 쓰는지 정리하고, includes와 indexOf, Map/Set 키 비교에서 생기는 프론트엔드 리뷰 포인트를 잡습니다.

26. 06. 24.SEOJing
JavaScript Number, safe integer, NaN, -0, BigInt를 한 장으로 정리한 다이어그램
Study26. 06. 23.

자바스크립트 퀴즈북 리마인드 Day 1: 숫자는 왜 가끔 믿을.

자바스크립트의 Number가 왜 정수처럼 보여도 부동소수점 모델 위에서 움직이는지, NaN과 -0, safe integer, BigInt를 프론트엔드 코드 리뷰 관점에서 다시 정리합니다.

26. 06. 23.SEOJing