이벤트 버블링, 캡쳐링과 위임
브라우저에서 동작하는 자바스크립트 코드에서 이벤트의 중요성은 가히 말할 필요도 없다. 자바스크립트의 다른 역할들도 많지만 사용자가 키보드를 눌렀을 때, 마우스를 클릭했을 때, 스크롤의 위치가 변했을 때 등 브라우저에서 사용자 동작으로 일어나는 거의 모든 일들은 event 객체에 의해 제어된다.
이벤트 제어의 기본적인 메커니즘은 1) DOM 요소에 이벤트를 등록 하고, 2) 등록한 이벤트가 발생했을 때 실행할 콜백 함수를 정의 해주면 된다.
위 예시에서는 button 태그에 click 이벤트가 발생했을 때, alert을 띄우는 콜백 함수를 등록했다.
이벤트 제어가 복잡해지는 이유는 무엇일까?
굉장히 간단해보이지만 이벤트 제어가 복잡해지는 이유 중 하나는 html 태그들의 중첩된 구조 때문이다.
다이어리 위에 책, 책 위에 마우스가 올려져 있을 때 사람이 마우스를 눌렀다면.. 마우스가 눌린 것인가? 책이 눌린 것인가? 다이어리가 눌린 것인가? 아니면 마우스와 책과 다이어리가 모두 눌린 것인가?
세 개의 div 태그가 중첩되어 있는 상황에서 특정 이벤트가 발생했을 때 브라우저 입장에서 어떻게 생각하는지 알아보자.
이벤트 버블링(Event Bubbling)
세 개의 div 태그에 click 이벤트가 발생했을 때, 이벤트가 발생한 태그 의 클래스 이름을 출력하는 콜백함수를 등록했다. 그리고 나는 세 번째 div 태그만 클릭했는데 콘솔창을 보면 div3 div2 div1
이 출력된 것을 볼 수 있다.
브라우저는 일단 확실하게 이벤트가 발생한 div3의 콜백함수를 실행하고, 상위에 있는 요소에 동일한 이벤트(click)가 등록 되어 있는지 탐색한다. 탐색 중 동일한 이벤트가 등록 되어 있는 요소가 있다면 해당 콜백 함수를 실행한다.
이벤트 버블링이란 특정한 요소에서 어떤 이벤트가 발생했을 때, 상위에 있는 요소까지 이벤트가 전파 되는 것을 말한다.
- 데모 링크를 수정하면서 직접 확인해보기
- div2를 클릭했을 때
- div1을 클릭했을 때
- div2의 이벤트를 focus로 바꾸고, div3을 클릭했을 때
- div2의 이벤트를 focus, div1의 이벤트를 blur로 바꾸고 div3을 클릭했을 때
이벤트 캡쳐링(Event Capturing)
버블링은 이벤트가 발생한 요소를 기준으로 가까운 상위 요소부터 하나씩 탐색한다면, 캡쳐링은 이벤트가 발생한 요소의 최상위 요소부터 처음 이벤트가 발생한 요소까지 탐색한다.
또한 이벤트 전파에서 버블링은 기본적으로 true여서 아무 설정 없이 발생한다. 이 흐름을 캡쳐링으로 변경하려면 이벤트 등록시 세 번째 인자로 { capture: true }
를 줘야 한다.
세 번째 div 태그를 클릭했을 때의 실행 결과를 보면 div3
이 먼저 출력되지 않는다. 최상위 요소인 div1
태그부터 처음 이벤트가 발생한 요소 div3
까지 다시 내려오면서 발생한 이벤트(click)와 동일한 이벤트가 등록 되어 있는 요소가 있는지 확인하고 콜백 함수를 실행한다. (사실 아직까지 캡쳐링은 한번도 사용해보지 않았다.. 언제 쓰나요?)
Event.stopPropagation()
이벤트 버블링이든 캡쳐링이든 html 요소가 복잡하게 중첩되어 있고, 동일한 요소에 이벤트가 여러 개 등록되어 있다면 전파가 어떻게 흘러가는지 파악하기 어렵다. 특정한 요소에 특정한 이벤트만 실행하고 전파되는 것을 막고 싶을 때, Event.stopPropagation()
함수를 사용하면 된다.
- 이벤트 버블링에 stopPropagation() 적용하기
- 이벤트 캡쳐링에 stopPropagation() 적용하기
이벤트 위임(Event Delegation)
하나의 div 태그 안에 여러 개의 button 태그가 있다고 상상해보자. button 태그를 클릭하면 자신의 innerHTML을 콘솔창에 출력하고 싶을 때, 아래와 같이 코드를 짤 수 있다. 이미지는 세 번째 button 태그를 클릭했을 때 이다.
하지만 만약 button 태그가 100개, 1000개 라면 이렇게 반복문을 돌려서 태그 하나하나에 이벤트를 등록하는 방법은 효율적이지 않다. 또한 브라우저에 같은 기능을 하는 button 태그가 동적으로 추가 되었다면 추가된 태그는 해당 이벤트가 등록되지 않아서 자신의 innerHTML 을 콘솔창에 출력하지 못한다.
앞서 설명한 이벤트 전파의 특성으로 이 문제를 해결할 수 있다.
button 하나하나에 이벤트를 등록하지 않고, button을 감싸고 있는 div 태그에만 이벤트를 등록하였다. 만약 사용자가 button을 클릭하면 이벤트 버블링 때문에 상위에 있는 요소까지 이벤트가 전파되어 콜백 함수가 실행된다. 처음으로 이벤트 위임에 대해 알았을 때, 아래에서 위로 전파되는 버블링 특성을 이용해 가장 상위 태그에 이벤트를 등록하여 해결하는 생각의 전환이 참신하다고 느꼈다. (참고로 캡쳐링으로 설정해도 이벤트 위임이 가능하다.)
e.target, e.currentTarget
예시로 설명한 코드를 보면 이벤트 버블링과 캡쳐링을 설명할 때는 e.currentTarget
을, 이벤트 위임을 설명할 때는 e.target
을 사용했다. 두 속성 모두 이벤트와 관련된 요소를 반환하지만 차이점이 있다.
-
e.target: 이벤트가 실제로 발생한 요소
-
e.currentTarget: 이벤트가 바인딩된 요소
자바스크립트에서 this 라는 개념과 연관되어 있는 현상 때문인데, 추후 작성할 글에서 알아보겠다. 이 글에서 제공한 3개의 데모링크에서 target을 currentTarget 으로, currentTarget을 target으로 바꿔보면 슬쩍 눈치 챌 수 있을 것이다.