자바스크립트에서 DOM 요소에 이벤트를 등록하고 동작을 확인해 보면, 때때로 기대하지 않은 방식으로 이벤트가 발생하거나, 상위 요소에서 이벤트가 작동하는 것을 경험하게 됩니다. 예를 들어, 버튼을 클릭했을 뿐인데 부모 요소의 클릭 이벤트가 함께 실행된다거나, 특정한 곳에만 이벤트가 적용되었으면 좋겠는데 다른 곳까지 영향을 미치는 것을 보게 됩니다. 초급자에게는 이런 현상이 자바스크립트가 “버그가 많은 언어”처럼 느껴질 수 있지만, 사실 이 모든 동작은 이벤트 전파(event propagation)라는 명확한 메커니즘에 기반합니다.
이벤트는 단순히 발생해서 끝나는 것이 아니라, DOM 트리를 따라 흐르며 전파되는 구조를 가지고 있습니다.

이 구조는 총 세 단계로 이루어져 있는데, 바로 캡처링(capturing) 단계, 타깃(target) 단계, 그리고 버블링(bubbling) 단계입니다. 많은 개발자들이 실무에서는 버블링만 고려하고 넘어가지만, 캡처링이 필요한 상황도 있으며, 이 두 개념을 정확히 이해해야만 이벤트 위임, 성능 최적화, 이벤트 차단 같은 기능들을 올바르게 설계할 수 있습니다.
이번 글에서는 자바스크립트의 이벤트 전파가 어떻게 이루어지는지, 캡처링과 버블링의 차이는 무엇인지, 그리고 실무에서 이 흐름을 어떻게 활용할 수 있는지를 다양한 코드 예제와 함께 살펴보겠습니다. 이벤트 흐름을 명확히 이해하면, 여러분의 DOM 설계와 인터랙션 구현 능력은 한 단계 더 성장하게 될 것입니다.
자바스크립트에서 이벤트는 단순히 특정 요소에만 머무르지 않고, DOM 트리를 따라 흐르는 특징을 가지고 있습니다. 이 흐름을 이해하면 이벤트가 어디서 시작되어 어디까지 전달되는지를 명확히 알 수 있으며, 실무에서 이벤트 위임이나, 이벤트 제어를 보다 정확하게 설계할 수 있습니다.

이벤트는 총 세 단계를 거쳐 흐릅니다. 첫 번째는 캡처링(capturing) 단계로, 이벤트가 최상위 요소(document)에서 시작하여 실제 타깃 요소까지 하위로 내려갑니다. 두 번째는 타깃(target) 단계로, 이벤트가 실제 발생한 요소에서 핸들러가 실행되는 시점입니다. 마지막은 버블링(bubbling) 단계로, 이벤트가 다시 타깃 요소에서 상위 요소로 역방향으로 올라가며 전파되는 단계입니다.

예를 들어, 다음과 같은 구조를 보겠습니다.
<div id="parent">
<button id="child">Click me</button>
</div>document.getElementById("parent").addEventListener("click", () => {
console.log("부모 요소 클릭");
});
document.getElementById("child").addEventListener("click", () => {
console.log("자식 요소 클릭");
});
버튼을 클릭하면 콘솔에는 다음과 같이 출력됩니다.
자식 요소 클릭
부모 요소 클릭
이는 타깃에서 실행된 후 이벤트가 부모 요소로 전파되었기 때문입니다. 이처럼 이벤트는 특정 요소에만 국한되지 않고, 트리를 따라 이동합니다. 이를 정확히 이해해야 이후의 이벤트 흐름 제어나 위임 전략을 수립할 수 있습니다.
이벤트가 전파되는 과정 중 캡처링과 버블링은 서로 반대 방향으로 이벤트를 감지하는 시점을 뜻합니다. 기본적으로 브라우저는 이벤트 버블링 단계에서 감지하지만, 필요에 따라 캡처링 단계에서 이벤트를 감지하도록 설정할 수도 있습니다. 두 단계의 차이를 이해하면, 이벤트 리스너를 더 정교하게 배치하고 예상치 못한 중복 실행을 피할 수 있습니다.
이벤트 리스너를 등록할 때 사용하는 addEventListener 메서드는 세 번째 인자로 useCapture라는 옵션을 받을 수 있습니다. 이 값을 true로 설정하면 캡처링 단계에서 이벤트를 감지하고, 기본값인 false는 버블링 단계에서 감지하게 됩니다.
parent.addEventListener("click", () => {
console.log("캡처링: 부모");
}, true);
child.addEventListener("click", () => {
console.log("버블링: 자식");
});
위 코드를 실행하고 버튼을 클릭하면 다음과 같은 순서로 로그가 출력됩니다.
캡처링: 부모
버블링: 자식
이처럼 두 핸들러는 같은 이벤트지만, 서로 다른 시점에 실행됩니다. 실무에서는 대부분 useCapture를 생략하고 버블링에만 의존하지만, 때로는 상위 요소에서 먼저 감지하고 조작을 막아야할 때 capture: true를 활용하기도 합니다.
이벤트 전파 흐름을 이해했다면, 이제 이를 제어할 수 있는 메서드도 함께 알아야 합니다. 가장 많이 쓰이는 event.stopPropagation()은 현재 이벤트 이후의 전파를 막습니다. 즉, 더 이상 상위 요소로 전달되지 않도록 차단하는 역할을 합니다.
child.addEventListener("click", (e) => {
e.stopPropagation();
console.log("자식에서 전파 중단");
});
이 코드가 실행되면 자식 요소의 핸들러만 실행되고, 부모 요소의 핸들러는 호출되지 않습니다. 반면 stopImmediatePropagation()은 같은 요소에 여러 개의 핸들러가 등록되어 있을 때, 나머지 핸들러까지 모두 실행을 막습니다.
또한 event.preventDefault()는 이벤트 전파와는 관계없이 해당 이벤트의 기본 동작을 막는 역할을 합니다. 예를 들어, <a> 태그의 링크 이동이나 <form>의 전송을 차단할 수 있습니다.
이벤트 전파를 이해하면 단순히 흐름을 알 수 있을 뿐만 아니라, 보다 효율적인 이벤트 처리 구조를 설계하는 데에도 활용할 수 있습니다. 그 대표적인 전략이 바로 이벤트 위임입니다. 이 방식은 DOM이 동적으로 변경되거나, 많은 요소가 반복적으로 생기는 상황에서 성능을 극대화하는 데 유리합니다.
이벤트 위임(Event Delegation)은 공통된 부모 요소에 하나의 리스너만 등록하고, 이벤트가 발생한 실제 자식 요소를 감지하여 처리하는 방식입니다. 이 방식은 이벤트가 버블링되기 때문에 가능하며, 불필요한 리스너 생성과 메모리 낭비를 줄여줍니다.
document.getElementById("menu").addEventListener("click", (e) => {
if (e.target.tagName === "LI") {
console.log("클릭된 항목:", e.target.textContent);
}
});
위처럼 ul 요소에만 리스너를 등록하면, li가 수십 개, 수백 개 있어도 하나의 리스너로 모두 처리할 수 있습니다. 실시간으로 추가되는 요소도 자동으로 감지되기 때문에 동적인 UI에 특히 강력한 방식입니다.
2) 고빈도 이벤트에서의 성능 최적화 전략
scroll, mousemove, resize처럼 자주 발생하는 이벤트는 작은 실수로도 성능 저하를 유발할 수 있습니다. 이런 이벤트를 여러 요소에 등록하면 렌더링이 지연되거나 CPU가 과도하게 사용될 수 있으므로, 반드시 위임 구조 또는 디바운싱/스로틀링과 같은 최적화 기법과 결합해야 합니다.
또한 이때는 이벤트 객체의 target과 currentTarget을 구분해서 잘 사용하는 것이 중요합니다. target은 실제 이벤트가 발생한 요소이고, currentTarget은 이벤트 핸들러가 등록된 요소입니다. 특히 위임 구조에서는 자식 요소에 따라 조건 분기를 하기 위해 e.target을 사용해야 하며, DOM 계층에 따라 예외 처리도 고려해야 합니다.
자바스크립트의 이벤트 전파 구조는 초보자에게 다소 낯설고 복잡하게 느껴질 수 있지만, 그 흐름을 제대로 이해하게 되면 DOM 구조를 보다 효율적으로 설계할 수 있는 강력한 도구가 됩니다. 이벤트는 단순히 특정 요소에서만 발생하고 끝나는 것이 아니라, DOM 트리를 따라 흐르며 캡처링 -> 타깃 -> 버블링의 3단계 과정을 거쳐 처리된다는 점을 꼭 기억해야 합니다.
이벤트가 위에서 아래로 내려가며 실행되는 캡처링 단계와, 다시 아래에서 위로 올라가는 버블링 단계는 단순한 이론이 아니라 실제 코드 실행 순서에 직접적인 영향을 줍니다. 이를 바탕으로 addEventListener의 capture 옵션을 설정하거나, 전파를 stopPropagation()으로 제어할 수 있으며, 기본 동작을 preventDefault()로 막는 등의 동작도 정확히 예측하고 설계할 수 있게 됩니다.
무엇보다 이 흐름은 실무에서 자주 활용되며, 많은 DOM 요소에 각각 이벤트를 등록하는 대신, 부모 요소 하나에만 이벤트를 위임하면 성능과 유지보수 측면에서 훨씬 유리합니다. 특히 React, Vue, Next.js 같은 프레임워크를 사용할 때도 기본은 결국 브라우저의 이벤트 전파 흐름을 이해하는 데서 시작합니다. 또한 고빈도 이벤트를 다룰 때는 이 흐름이 성능 저하의 원인이 될 수도 있으므로, 위임 전략과 디바운싱/스로틀링 기법을 함께 사용하는 것이 중요합니다. 이를 통해 의도하지 않은 이벤트 중첩 실행이나 렌더링 병목을 방지할 수 있습니다.
마지막으로, 이벤트 객체의 target과 currentTarget을 정확히 이해하고 구분해서 사용하는 습관도 매우 중요합니다. 이 작은 차이를 구분하지 못하면, 이벤트 위임 구조에서 예상치 못한 동작이 발생하거나, 전파 제어에 실패할 수 있기 때문입니다.
이제부터는 단순히 클릭 이벤트를 등록하는 수준을 넘어서, 이벤트가 언제, 어디서, 어떻게 실행되는지를 예측하고 설계할 수 있는 개발자로 성장해 보시길 바랍니다. 이벤트 흐름을 이해하는 것은 곧 DOM의 흐름을 이해하는 일이며, 그만큼 프론트엔드 개발의 기반을 더욱 단단하게 만들어 줄 겁니다.
©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.