티스토리 뷰

현대 웹 애플리케이션은 사용자와의 끊임없는 상호작용으로 이루어집니다. 버튼 클릭 하나, 스크롤 한 번이 모두 '이벤트'이며, 이를 어떻게 처리하느냐에 따라 서비스의 성능과 사용자 경험(UX)이 결정됩니다. 단순히 "클릭하면 실행된다"는 수준을 넘어, 브라우저가 이벤트를 전파하는 원리를 이해하면 수백 개의 리스트 아이템을 단 하나의 함수로 제어하는 마법 같은 최적화가 가능해집니다.
1. 핵심 개념: 이벤트는 어떻게 흐르는가? (Deep Dive)
브라우저는 특정 요소에서 이벤트가 발생했을 때, 이를 단순히 해당 요소에만 알리지 않습니다. HTML 구조는 트리(Tree) 형태이기 때문에, 이벤트는 뿌리부터 가지 끝까지, 다시 가지 끝에서 뿌리까지 순환합니다.
이벤트 전파의 3단계
- 캡처링(Capturing): window에서 시작해 이벤트가 발생한 타겟 요소까지 내려가는 단계.
- 타겟(Target): 실제 이벤트가 발생한 요소에 도달하는 단계.
- 버블링(Bubbling): 타겟 요소에서 다시 최상위 부모까지 거품이 올라가듯 전달되는 단계.
💡 비유로 이해하기: "전교 회장 선거 결과 공고" 전교 회장 선거 결과가 나왔다고 가정해 봅시다.
- 교장 선생님이 각 반에 결과를 전달하러 내려가는 과정이 캡처링입니다.
- 마침내 당선된 학생이 있는 반에 도착하는 것이 타겟 단계입니다.
- 당선 소식을 들은 학생이 기뻐하며 소리를 지르자, 그 소리가 복도를 타고 위층 교무실까지 들리는 과정이 바로 버블링입니다.
실제 실무에서 우리가 작성하는 대부분의 로직은 이 버블링 단계에서 동작합니다.
2. 실전 예제: 동적 장바구니 리스트 관리 (Hands-on)
이커머스 서비스에서 상품 리스트가 동적으로 추가되거나 삭제될 때, 각 삭제 버튼마다 리스너를 다는 것은 메모리 낭비입니다. 이를 해결하기 위해 이벤트 위임(Event Delegation) 패턴을 적용해 보겠습니다.
// HTML 구조 예시: <ul id="product-list"> <li data-id="1">상품 A <button class="delete-btn">삭제</button></li> ... </ul>
const productList = document.querySelector('#product-list');
// 각 버튼이 아닌, 부모 요소(ul)에 단 하나의 리스너만 등록합니다.
productList.addEventListener('click', (event) => {
// 1. 실제 클릭된 요소(target)가 삭제 버튼인지 확인합니다.
const isDeleteButton = event.target.classList.contains('delete-btn');
if (!isDeleteButton) return; // 삭제 버튼이 아니면 무시
// 2. 가장 가까운 li 요소를 찾아 상품 ID를 가져옵니다.
const productItem = event.target.closest('li');
const productId = productItem.dataset.id;
// 3. 비즈니스 로직 실행 (예: 삭제 API 호출 및 DOM 제거)
console.log(`상품 ${productId}를 삭제합니다.`);
removeProductFromServer(productId);
productItem.remove();
});
/**
* @tip 트러블슈팅: event.target vs event.currentTarget
* - event.target: 실제 클릭된 '가장 안쪽'의 요소 (버튼 혹은 버튼 내부의 아이콘).
* - event.currentTarget: 이벤트 리스너가 실제로 부착된 요소 (여기서는 ul).
* 만약 버튼 안에 <span> 아이콘이 있다면 target이 span이 될 수 있으므로
* .closest() 메서드를 사용하여 정확한 부모 요소를 찾는 것이 안전합니다.
*/
3. 기술적 한계와 고려사항 (Trade-offs)
이벤트 버블링과 위임은 강력하지만, 모든 상황에서 정답은 아닙니다.
- 성능의 양면성: 수천 개의 요소에 리스너를 다는 것보다 위임이 메모리 측면에서 훨씬 유리합니다. 하지만 이벤트가 부모로 거슬러 올라가는 과정에서 많은 연산을 수행하거나, document 레벨까지 이벤트를 올리면 오히려 전체적인 반응성이 떨어질 수 있습니다.
- 전파 중단의 위험성: event.stopPropagation()은 버블링을 막아주지만, 이는 신중해야 합니다. 다른 라이브러리(예: 분석 도구, 모달 닫기 로직)가 상위에서 이벤트를 감지해야 할 때 이를 차단해 버리는 부작용이 생길 수 있기 때문입니다.
- 포커스 관련 이벤트: focus나 blur 이벤트는 기본적으로 버블링되지 않습니다. 이런 경우 focusin, focusout을 사용하거나 캡처링 단계에서 이벤트를 가로채야 합니다.
4. 요약 및 제언
이벤트 리스너의 동작 원리를 이해하는 것은 단순히 기능을 구현하는 것을 넘어, 확장 가능하고 성능 최적화된 아키텍처를 설계하는 첫걸음입니다.
- 버블링은 하위 요소의 이벤트를 상위에서 감지할 수 있게 해주는 브라우저의 기본 메커니즘입니다.
- 이벤트 위임을 통해 동적으로 추가되는 요소에 대응하고 메모리 사용량을 절감하세요.
- event.target을 다룰 때는 구조적 유연성을 위해 .closest()와 같은 메서드를 적극 활용하십시오.
지금 진행 중인 프로젝트의 리스트 구조를 살펴보세요. 혹시 반복문을 돌며 수많은 addEventListener를 호출하고 있지는 않나요? 부모 요소로 리스너를 옮겨보는 것만으로도 코드의 간결함이 달라질 것입니다.
- Total
- Today
- Yesterday
- Javascript
- 엣지컴퓨팅
- 카카오
- 스마트안경
- SSR
- sLLM
- MSA
- CSS
- HTML
- java
- TypeScript
- HBM
- AI
- Rag
- Nextjs
- 멀티모달
- react
- prompt engineering
- CSR
- It용어
- on-device ai
- 구글
- LLM
- 웹기초
- 협력
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |