event.target vs event.currentTarget: JavaScript 이벤트 처리의 핵심 개념 완전 정복

2025. 6. 11. 11:00·Frontend Development
반응형

들어가며

프론트엔드 개발을 하다 보면 이벤트 처리에서 event.target과 event.currentTarget을 자주 마주치게 됩니다. 하지만 이 둘의 차이점을 정확히 모르면 예상과 다른 동작으로 인해 버그가 발생할 수 있습니다. 이 글에서는 두 속성의 차이점과 실무에서의 활용법을 체계적으로 알아보겠습니다.


기본 개념: event.target과 event.currentTarget이란?

정의

속성 의미 역할
event.target 이벤트가 실제로 발생한 요소 사용자가 직접 상호작용한 요소
event.currentTarget 이벤트 리스너가 연결된 요소 이벤트를 듣고 있는 요소

💡 핵심 포인트: target은 "누가 이벤트를 일으켰나?", currentTarget은 "누가 이벤트를 처리하나?"를 나타냅니다.

실생활 비유: 회사 조직의 보고 체계

이해를 돕기 위해 회사 조직 구조에 비유해보겠습니다.

상황: 신입사원이 문제를 발견하고 부장에게 보고하는 경우

  • event.target (신입사원): 실제로 문제를 발견한 사람
  • event.currentTarget (부장): 보고를 받고 처리하는 사람
  • 이벤트 버블링: 신입 → 대리 → 과장 → 부장으로 전달되는 과정

이벤트 버블링과 함께 이해하기

이벤트 버블링이란?

이벤트 버블링(Event Bubbling)은 이벤트가 발생한 요소에서 시작하여 부모 요소들로 차례대로 전파되는 현상입니다.

<div id="parent">
  <div id="child">
    <button id="button">클릭하세요</button>
  </div>
</div>

버블링 과정:

1. button에서 클릭 이벤트 발생
   ↓
2. child div로 버블링
   ↓  
3. parent div로 버블링
   ↓
4. document까지 버블링

실제 동작 예시

// HTML 구조
// <div class="container">
//   <div class="card">
//     <button class="btn">클릭</button>
//   </div>
// </div>

const container = document.querySelector('.container');
const card = document.querySelector('.card');
const button = document.querySelector('.btn');

// 각 요소에 이벤트 리스너 추가
container.addEventListener('click', function(event) {
  console.log('Container 클릭됨');
  console.log('target:', event.target.className);           // 실제 클릭된 요소
  console.log('currentTarget:', event.currentTarget.className); // 현재 처리하는 요소
});

card.addEventListener('click', function(event) {
  console.log('Card 클릭됨');
  console.log('target:', event.target.className);
  console.log('currentTarget:', event.currentTarget.className);
});

button.addEventListener('click', function(event) {
  console.log('Button 클릭됨');
  console.log('target:', event.target.className);
  console.log('currentTarget:', event.currentTarget.className);
});

버튼 클릭 시 출력 결과:

Button 클릭됨
target: btn
currentTarget: btn

Card 클릭됨  
target: btn
currentTarget: card

Container 클릭됨
target: btn
currentTarget: container

실무에서의 활용 사례

1. 이벤트 위임 (Event Delegation)

이벤트 위임은 부모 요소에 이벤트 리스너를 등록하여 자식 요소들의 이벤트를 처리하는 기법입니다.

<ul class="todo-list">
  <li><button class="delete-btn" data-id="1">삭제</button> 할일 1</li>
  <li><button class="delete-btn" data-id="2">삭제</button> 할일 2</li>
  <li><button class="delete-btn" data-id="3">삭제</button> 할일 3</li>
  <!-- 동적으로 추가되는 항목들... -->
</ul>
// ❌ 비효율적인 방법: 각 버튼마다 이벤트 리스너 등록
document.querySelectorAll('.delete-btn').forEach(btn => {
  btn.addEventListener('click', function(e) {
    const id = e.target.dataset.id;
    deleteTodo(id);
  });
});

// ✅ 효율적인 방법: 이벤트 위임 사용
document.querySelector('.todo-list').addEventListener('click', function(e) {
  // event.target을 사용하여 실제 클릭된 요소 확인
  if (e.target.classList.contains('delete-btn')) {
    const id = e.target.dataset.id;
    deleteTodo(id);
  }
});

이벤트 위임의 장점:

  • 메모리 효율성 (하나의 리스너만 등록)
  • 동적으로 추가되는 요소에도 자동 적용
  • 코드 유지보수성 향상

2. 모달 창 외부 클릭 처리

<div class="modal-overlay">
  <div class="modal-content">
    <h2>모달 제목</h2>
    <p>모달 내용</p>
    <button class="close-btn">닫기</button>
  </div>
</div>
const modalOverlay = document.querySelector('.modal-overlay');

modalOverlay.addEventListener('click', function(e) {
  // currentTarget: 이벤트 리스너가 등록된 overlay
  // target: 실제 클릭된 요소

  if (e.target === e.currentTarget) {
    // 오버레이 자체를 클릭한 경우 (모달 외부)
    closeModal();
  }
  // 모달 내부를 클릭한 경우는 아무것도 하지 않음
});

function closeModal() {
  modalOverlay.style.display = 'none';
}

3. 드롭다운 메뉴 처리

<div class="dropdown">
  <button class="dropdown-toggle">메뉴 ▼</button>
  <ul class="dropdown-menu">
    <li><a href="#" data-action="edit">편집</a></li>
    <li><a href="#" data-action="delete">삭제</a></li>
    <li><a href="#" data-action="share">공유</a></li>
  </ul>
</div>
const dropdown = document.querySelector('.dropdown');
const dropdownMenu = document.querySelector('.dropdown-menu');

// 드롭다운 토글
dropdown.addEventListener('click', function(e) {
  if (e.target.matches('.dropdown-toggle')) {
    dropdownMenu.classList.toggle('show');
  }
});

// 메뉴 항목 선택 처리
dropdown.addEventListener('click', function(e) {
  // event.target을 사용하여 실제 클릭된 링크 확인
  if (e.target.matches('a[data-action]')) {
    e.preventDefault();

    const action = e.target.dataset.action;
    handleMenuAction(action);

    // 메뉴 닫기
    dropdownMenu.classList.remove('show');
  }
});

function handleMenuAction(action) {
  switch(action) {
    case 'edit':
      console.log('편집 기능 실행');
      break;
    case 'delete':
      console.log('삭제 기능 실행');
      break;
    case 'share':
      console.log('공유 기능 실행');
      break;
  }
}

자주 발생하는 실수와 해결법

1. this vs event.currentTarget vs event.target

const buttons = document.querySelectorAll('.btn');

buttons.forEach(btn => {
  btn.addEventListener('click', function(e) {
    console.log('this:', this);                    // 이벤트 리스너가 등록된 요소 (btn)
    console.log('currentTarget:', e.currentTarget); // 이벤트 리스너가 등록된 요소 (btn)  
    console.log('target:', e.target);              // 실제 클릭된 요소 (span일 수도 있음)

    // this와 currentTarget은 항상 같음
    console.log(this === e.currentTarget); // true
  });
});

주의사항:

<button class="btn">
  <span class="icon">🔥</span>
  <span class="text">클릭하세요</span>  
</button>
document.querySelector('.btn').addEventListener('click', function(e) {
  console.log('target:', e.target.className);        // 'icon' 또는 'text' 또는 'btn'
  console.log('currentTarget:', e.currentTarget.className); // 항상 'btn'

  // ❌ 잘못된 방법: target을 사용하면 span을 클릭했을 때 문제 발생
  if (e.target.classList.contains('btn')) {
    handleClick();
  }

  // ✅ 올바른 방법: currentTarget 사용
  if (e.currentTarget.classList.contains('btn')) {
    handleClick();
  }
});

2. 화살표 함수에서의 this

const button = document.querySelector('.btn');

// 일반 함수: this가 currentTarget과 같음
button.addEventListener('click', function(e) {
  console.log(this === e.currentTarget); // true
});

// 화살표 함수: this가 상위 스코프를 가리킴
button.addEventListener('click', (e) => {
  console.log(this === e.currentTarget); // false
  console.log(this === window);          // true (브라우저 환경)

  // 화살표 함수에서는 e.currentTarget 사용 권장
  console.log(e.currentTarget);
});

고급 활용: 커스텀 이벤트 시스템

복잡한 컴포넌트에서의 활용

class TabComponent {
  constructor(container) {
    this.container = container;
    this.activeTab = null;
    this.init();
  }

  init() {
    // 이벤트 위임을 사용한 탭 처리
    this.container.addEventListener('click', (e) => {
      const target = e.target;
      const currentTarget = e.currentTarget;

      // 탭 헤더 클릭 처리
      if (target.matches('.tab-header')) {
        this.handleTabClick(target);
      }

      // 탭 닫기 버튼 클릭 처리
      if (target.matches('.tab-close')) {
        e.stopPropagation(); // 부모 탭 클릭 이벤트 방지
        this.handleTabClose(target.closest('.tab-header'));
      }
    });
  }

  handleTabClick(tabElement) {
    // 기존 활성 탭 제거
    if (this.activeTab) {
      this.activeTab.classList.remove('active');
    }

    // 새 탭 활성화
    tabElement.classList.add('active');
    this.activeTab = tabElement;

    // 탭 내용 표시
    this.showTabContent(tabElement.dataset.tabId);
  }

  handleTabClose(tabElement) {
    if (tabElement === this.activeTab) {
      // 활성 탭을 닫는 경우 다른 탭으로 전환
      const nextTab = tabElement.nextElementSibling || tabElement.previousElementSibling;
      if (nextTab) {
        this.handleTabClick(nextTab);
      }
    }

    tabElement.remove();
  }

  showTabContent(tabId) {
    // 모든 탭 내용 숨기기
    this.container.querySelectorAll('.tab-content').forEach(content => {
      content.style.display = 'none';
    });

    // 선택된 탭 내용 표시
    const targetContent = this.container.querySelector(`[data-content-id="${tabId}"]`);
    if (targetContent) {
      targetContent.style.display = 'block';
    }
  }
}

// 사용 예시
const tabContainer = document.querySelector('.tab-container');
const tabs = new TabComponent(tabContainer);

성능 최적화와 베스트 프랙티스

1. 이벤트 리스너 최적화

// ❌ 비효율적: 각 요소마다 리스너 등록
document.querySelectorAll('.card').forEach(card => {
  card.addEventListener('click', handleCardClick);
});

// ✅ 효율적: 이벤트 위임 사용
document.body.addEventListener('click', function(e) {
  if (e.target.closest('.card')) {
    handleCardClick(e);
  }
});

function handleCardClick(e) {
  const card = e.target.closest('.card');
  // card 처리 로직
}

2. 이벤트 전파 제어

const form = document.querySelector('.form');
const submitBtn = document.querySelector('.submit-btn');

form.addEventListener('click', function(e) {
  // 폼 영역 클릭 시 일반적인 처리
  console.log('Form area clicked');
});

submitBtn.addEventListener('click', function(e) {
  // 제출 버튼 클릭 시 특별한 처리
  e.stopPropagation(); // 부모의 클릭 이벤트 방지

  if (validateForm()) {
    submitForm();
  }
});

정리 및 핵심 인사이트

핵심 요약

  1. event.target: 이벤트가 실제로 발생한 요소 (사용자가 직접 클릭한 요소)
  2. event.currentTarget: 이벤트 리스너가 등록된 요소 (이벤트를 처리하는 요소)
  3. 이벤트 버블링: 자식에서 부모로 이벤트가 전파되는 과정
  4. 이벤트 위임: 부모 요소에서 자식 요소들의 이벤트를 처리하는 기법

개발자를 위한 실무 인사이트

언제 어떤 속성을 사용할까?

상황 사용 속성 이유
이벤트 위임 event.target 실제 클릭된 자식 요소를 식별해야 함
모달 외부 클릭 event.target === event.currentTarget 오버레이 자체 클릭 여부 판단
버튼 스타일링 event.currentTarget 버튼 내 아이콘 클릭 시에도 일관된 동작
폼 요소 처리 event.currentTarget 폼 전체의 상태 관리

성능 최적화 팁:

  • 이벤트 위임을 활용하여 메모리 사용량 감소
  • stopPropagation()으로 불필요한 이벤트 전파 방지
  • preventDefault()로 기본 동작 제어
  • closest() 메서드로 상위 요소 탐색 최적화

실무에서 자주 하는 실수:

  • 화살표 함수에서 this 대신 event.currentTarget 사용
  • 중첩된 요소에서 target vs currentTarget 혼동
  • 이벤트 위임 시 matches() 메서드 누락

event.target과 event.currentTarget의 차이를 정확히 이해하면, 더 효율적이고 유지보수하기 쉬운 이벤트 처리 코드를 작성할 수 있습니다. 특히 현대 웹 개발에서 필수적인 이벤트 위임 패턴을 마스터하는 핵심 개념입니다.


참고 자료

  • MDN - Event.target
  • MDN - Event.currentTarget
  • MDN - Event Bubbling and Capturing
  • JavaScript.info - Bubbling and capturing
  • W3C DOM Events Specification
  • Event Delegation Pattern - JavaScript Patterns
반응형
저작자표시 비영리 변경금지 (새창열림)

'Frontend Development' 카테고리의 다른 글

HTML DOCTYPE 완전 정복: 웹 브라우저의 첫 번째 선택지  (6) 2025.06.12
URI vs URL vs URN: 웹 자원 식별의 핵심 개념 완전 정복  (0) 2025.06.11
CDN(Content Delivery Network) 완전 정복: 웹 성능 최적화의 핵심 기술  (0) 2025.06.11
TypeScript any vs 제네릭 T: 실행 결과로 보는 확실한 차이점  (0) 2025.06.09
TypeScript 제네릭 완전 정복: 실행 결과로 배우는 실전 가이드  (0) 2025.06.09
'Frontend Development' 카테고리의 다른 글
  • HTML DOCTYPE 완전 정복: 웹 브라우저의 첫 번째 선택지
  • URI vs URL vs URN: 웹 자원 식별의 핵심 개념 완전 정복
  • CDN(Content Delivery Network) 완전 정복: 웹 성능 최적화의 핵심 기술
  • TypeScript any vs 제네릭 T: 실행 결과로 보는 확실한 차이점
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (100) N
      • Frontend Development (39) N
      • Backend Development (21) N
      • Algorithm (33) N
        • 백준 (11) N
        • 프로그래머스 (17)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (3)
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    tailwindcss
    frontend development
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
event.target vs event.currentTarget: JavaScript 이벤트 처리의 핵심 개념 완전 정복
상단으로

티스토리툴바