이벤트 버블링과 캡처링, 그리고 이벤트 위임

이벤트 버블링? 캡처링?

우리는 자바스크립트를 통해 이벤트 핸들링을 할 때, 이벤트가 다른 요소로 전파되는 현상을 자주 겪는다.
이는 이벤트 버블링 또는 캡처링이라는 “이벤트 전파” 현상 때문이다.

그렇다면 이벤트 버블링과 캡처링은 정확히 무엇을 의미할까?

이는 생각보다 쉽게 이해하고 기억할 수 있다. 버블 = 거품을 떠올리면 이벤트 버블링을 쉽게 떠올릴 수 있고, 이벤트 버블링만 잘 기억하면 이와 반대되는 방향성을 가진 캡처링을 쉽게 기억해낼 수 있기 때문이다.

위의 사진처럼, 거품은 위로 솟아 오른다.
이와 동일한 맥락으로써, 이벤트 버블링은 이벤트가 상향으로 전파되는 것을 의미한다.
따라서, 이와 반대로 이벤트 캡처링은 상위 요소에서 하위 요소로 이벤트가 하향 전파되는 것을 뜻한다.

즉, 위의 그림과 같이 이벤트 버블링은 상향 전파되는 방식을, 이벤트 캡처링은 하향 전파되는 이벤트 전파 방식이라고 이해하면 된다.


캡처링과 버블링의 활용

그렇다면 어떻게 우리는 이벤트 캡처링과 버블링을 활성화 또는 비활성화 하며 활용할 수 있을까?

결론부터 말하자면, 이벤트 버블링은 특정한 경우를 제외하곤 기본적으로 활성화 되어있다. 하지만 이와 반대로, 이벤트 캡처링은 기본적으로 비활성화되어 있기 때문에, 캡처링을 활성화 하기 위해선 수동적으로 설정을 해줘야 한다.

이벤트 버블링이 기본적으로 설정되지 않은 이벤트들

위에서 설명한 바와 같이, 이벤트 버블링이 디폴트로 설정되어 있지 않은 이벤트들은 이하와 같다.

이벤트 종류 이벤트
포커스 이벤트 focus/blur
리소스 이벤트 load/unload/abort/error
마우스 이벤트 mouseenter/mouseleave

위의 요소들의 버블링을 설정하고 싶다면, 캡처링을 통해 상위 요소에서 해당 이벤트가 캡처될 수 있도록 할 수 있을 것이다. 하지만, 위의 이벤트들을 굳이 캡처링하기 보다는, 그냥 위의 이벤트와 닮은 꼴인 이벤트들을 활용하면 쉽게 버블링을 유도 및 활용할 수 있다.

focus/blur의 경우 focusin/focusout으로 대체하면 이벤트 버블링을 기본적으로 활용할 수 있고, mouseenter/mouseleave는 mouseover/mouseout으로 대체할 수 있다.

이벤트 캡처링은 어떻게 설정하는가?

이제 버블링에 대해서는 알겠는데,
그렇다면 이벤트 캡처링은 어떻게 수동적으로 설정하는 걸까?

아래와 같이 세 번째 인자에 true를 작성해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1
OOOO.addEventListener(
'click',
(e) => {
/* 생략 */
},
true
);

// 2
OOOO.addEventListener(
'click',
(e) => {
/* 생략 */
},
{ capture: true }
);

이벤트 버블링 막기

이벤트 버블링은 거의 대부분 기본적으로 설정되어 있기 때문에, 이를 수동적으로 해제하는 방법 또한 반드시 짚고 넘어가야 한다.

1
2
3
4
5
const handleClick = (e) => {
e.stopPropagation(); // stopPropagation의 활용
};

OOO.addEventListener('click', handleClick);

위의 코드 예시와 같이, event.stopPropagation()을 이벤트 핸들러 내에 작성해주면 이벤트 버블링이 발생하는 것을 수동적으로 차단할 수 있다.

근데… 이게 왜 필요한걸까?

솔직히 여기까지의 개념만 놓고 봤을 때에는, 버블링과 캡처링이 도대체 왜 필요한지 모르겠다.

이걸 왜 만들었지...?

각설하고 바로 결론부터 말하자면, “이벤트 위임”을 위해 존재한다고 봐도 과언이 아니다. 이벤트 전파를 통해 이벤트 위임이라는 효율적이고 편리한 기능 또는 동작의 구현을 기대할 수 있기 때문이다. 바로 아래에서 이벤트 위임에 대해 알아가보도록 하자 🙂


이벤트 위임

우리는 이벤트 위임을 통해, 하위 요소들에 이벤트를 덕지덕지 붙이지 않고 상위 요소만으로도 쉽게 하위 요소들의 이벤트를 핸들링할 수 있다.

바로 코드를 통해 위 정의를 이해해보도록 하자!

1
2
3
4
<ul class='todoList'>
<li class='list1'>독서</li>
<li class='list2'>조깅하기</li>
</ul>
1
2
3
4
5
6
7
const lists = document.querySelectorAll('input');

lists.forEach(function (list) {
list.addEventListener('click', () => {
/* 생략 */
});
});

위와 같은 코드가 있었다고 가정하고, 각 <li>들을 클릭하면 이벤트핸들러가 잘 동작할 것이다.

하지만, 만약 나중에 <li class=”listN”>가 동적으로 추가된다면 어떻게 될까?

그 때에는 뒤에 추가된 <li> 태그들에는 이벤트 리스너가 등록되지 않았기 때문에, 수동으로 다시 코드 중간에 이벤트 리스너를 등록해주는 과정을 거쳐야 되는 큰 불편함이 있을 것이다.

이벤트 위임의 활용

위와 같은 불편사항 또는 문제점이 발생할 수 있기 때문에, 우리는 이벤트 위임을 활용해야 한다.

이벤트 위임은 아래와 같이 활용할 수 있다.

1
2
3
4
5
const todoList = document.querySelector('.todoList');

todoList.addEventListener('click', () => {
/* 생략 */
});

위 코드와 같이, 상위요소 <ul>태그에만 이벤트리스너를 달아두면, 하위 요소인 <li>에서 클릭 이벤트가 발생해도 이벤트 버블링에 의해 <ul>의 이벤트 핸들러가 호출될 수 있다.

만약 이벤트 버블링이라는 이벤트 전파의 동작이 없었다면, 우리는 새로운 요소들이 추가될 때마다 이벤트 리스너를 계속 수동적으로 할당해줘야 하는 큰 불편함을 겪어야 했을 것이다. 하지만, 이제 우리는 이벤트 위임을 통해 상위 요소에만 이벤트 리스너를 할당한 뒤, 하위 요소들의 이벤트 핸들링을 효율적이고 쉽게 관리할 수 있다 😊

추가 예제

마지막으로 아래의 예제 코드를 한 번만 더 확인해보자 (HTML 코드는 위와 동일하다)

1
2
3
4
5
6
7
8
const todoList = document.querySelector('.todoList');

const handleClick = (e) => {
console.log(e.currentTarget); // ul태그가 출력될 것이다.
console.log(e.target); // 실제로 클릭한 li태그가 출력될 것이다.
};

todoList.addEventListener('click', handleClick);

이벤트 버블링과 캡처링, 그리고 이벤트 위임

https://hoonjoo-park.github.io/javascript/base/bubblingAndCapturing/

Author

Hoonjoo

Posted on

2022-05-11

Updated on

2022-05-11

Licensed under

Comments