반응형
들어가며
프론트엔드 개발을 하다 보면 이벤트 처리에서 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();
}
});
정리 및 핵심 인사이트
핵심 요약
- event.target: 이벤트가 실제로 발생한 요소 (사용자가 직접 클릭한 요소)
- event.currentTarget: 이벤트 리스너가 등록된 요소 (이벤트를 처리하는 요소)
- 이벤트 버블링: 자식에서 부모로 이벤트가 전파되는 과정
- 이벤트 위임: 부모 요소에서 자식 요소들의 이벤트를 처리하는 기법
개발자를 위한 실무 인사이트
언제 어떤 속성을 사용할까?
상황 | 사용 속성 | 이유 |
---|---|---|
이벤트 위임 | event.target |
실제 클릭된 자식 요소를 식별해야 함 |
모달 외부 클릭 | event.target === event.currentTarget |
오버레이 자체 클릭 여부 판단 |
버튼 스타일링 | event.currentTarget |
버튼 내 아이콘 클릭 시에도 일관된 동작 |
폼 요소 처리 | event.currentTarget |
폼 전체의 상태 관리 |
성능 최적화 팁:
- 이벤트 위임을 활용하여 메모리 사용량 감소
stopPropagation()
으로 불필요한 이벤트 전파 방지preventDefault()
로 기본 동작 제어closest()
메서드로 상위 요소 탐색 최적화
실무에서 자주 하는 실수:
- 화살표 함수에서
this
대신event.currentTarget
사용 - 중첩된 요소에서
target
vscurrentTarget
혼동 - 이벤트 위임 시
matches()
메서드 누락
event.target과 event.currentTarget의 차이를 정확히 이해하면, 더 효율적이고 유지보수하기 쉬운 이벤트 처리 코드를 작성할 수 있습니다. 특히 현대 웹 개발에서 필수적인 이벤트 위임 패턴을 마스터하는 핵심 개념입니다.
참고 자료
반응형
'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 |