📢 어렵고 정석적인 개념 설명보다는 저같은 초보자도 이해하기 쉽게 정리하는 것을 원칙으로 포스팅하고 있습니다. 😄
What
자바스크립트에서는 addEventListener()를 통해 해당 요소를 클릭했을 때 특정 이벤트를 발생시킬 수 있다. 만약, 부모-자식 요소 관계에서 부모 요소와 자식 요소 둘 다 이벤트 리스너를 적용했을 때(전제 조건) 이 중 하나를 클릭하게 되면 어떤 이벤트 리스너가 먼저 작동하게 될까?
<div class="box1">
<div class="box2">
<div class="box3"></div>
</div>
</div>
(아래의 예시들은 위와 같은 구조의 DOM 요소를 가지고 있다고 가정한다.)
How
이벤트 버블링(Event Bubbling) : 아래 → 위
const box1 = document.querySelector('.box1');
const box2 = document.querySelector('.box2');
const box3 = document.querySelector('.box3');
box1.addEventListener('click', () => alert('box1'));
box2.addEventListener('click', () => alert('box2'));
box3.addEventListener('click', () => alert('box3'));
버블링은 가장 일반적으로 사용되는 이벤트 원리 중 하나이며, 웬만한 자바스크립트 이벤트는 거의 버블링이라고 봐도 무방할 정도로 가장 많이 쓰이는 방식이다. 밑에서 위로 올라오는 버블과 같다고 해서 이름이 버블링인 이 원리는 부모-자식 요소 관계에서 자식의 이벤트 리스너가 먼저 작동이 되고, 그 뒤로 부모의 이벤트 리스너가 작동이 되는 것을 말한다.
위와 같은 경우에는 box3의 이벤트 리스너가 먼저 작동되고, 그다음으로 box2, box1의 이벤트 리스너가 순차적으로 작동된다. 즉, 한 번 클릭으로 3개의 이벤트 리스너가 작동하는 것이다.
이벤트 캡쳐링(Event Capturing) : 위 → 아래
const box1 = document.querySelector('.box1');
const box2 = document.querySelector('.box2');
const box3 = document.querySelector('.box3');
box1.addEventListener('click', () => alert('box1'), {capture: true});
box2.addEventListener('click', () => alert('box2'), true); // capture 생략 가능
box3.addEventListener('click', () => alert('box3'), true);
캡쳐링은 버블링과 반대라고 생각하면 된다. 부모 요소의 이벤트 리스너가 먼저 작동되고, 그 뒤로 자식 요소의 이벤트 리스너가 작동된다. 보통은 버블링이 디폴트이기 때문에 캡쳐링을 사용하려면 addEventListener()의 3번째 인자로 capture라는 값을 넣어줘야 한다. {capture: true} 혹은 true를 입력하면 해당 이벤트 리스너는 버블링이 아닌 캡쳐링의 원리를 따르게 된다.
이벤트 버블링을 사용하기 싫을 때 : e.stopPropagation()
const box1 = document.querySelector('.box1');
const box2 = document.querySelector('.box2');
const box3 = document.querySelector('.box3');
box1.addEventListener('click', () => alert('box1'));
box2.addEventListener('click', () => alert('box2'));
box3.addEventListener('click', (e) => {
alert('box3');
e.stopPropagation(); // 이벤트 버블링 차단
});
일반적이라면 이벤트 버블링으로 인해 box3을 클릭했을 때 box2, box1의 이벤트 리스너도 순차적으로 동작한다는 것을 알고 있을 것이다. 하지만, 가끔 이런 이벤트 전달방식은 오히려 우리에게 방해가 될 수도 있다. 이럴 때 사용하는 것이 바로 e.stopPropagation()이다. box3를 클릭했을 때 box2로 올라가려는 버블을 터뜨려주는 기능으로 버블링과 캡쳐링을 방지하고 싶을 때 사용한다.
이벤트 위임 (Event Delegation)
box1.addEventListener('click', (e) => {
const class = e.target.className;
if (class === 'box1') alert('box1');
if (class === 'box2') alert('box2');
if (class === 'box3') alert('box3');
})
우리는 버블링으로 인해 배운 것이 하나 있다. 하위 요소들은 상위 요소의 이벤트를 인식한다는 점이다. 왜냐하면 버블링 자체가 자신을 포함해서 상위 요소의 이벤트 리스너까지 모두 작동시킨다는 것 자체가 상위 요소의 이벤트를 인식한다는 뜻이기 때문이다.
이것을 이용하면 위의 코드와 같이 이벤트 리스너를 최상위 요소에만 줘도 하위 요소에도 마치 이벤트 리스너를 준 것처럼 작동시킬 수 있다. 상위 요소를 클릭했을 때 하위 요소가 동작하는 것은 캡쳐링의 원리가 아니냐 싶겠지만, 캡쳐링은 단순히 이벤트 리스너의 동작 순서가 상위 요소에서 하위 요소로 동작하는 것을 의미할 뿐이지 하위 요소가 상위 요소의 이벤트 리스너를 인식한다는 의미가 아니다. 오히려 이것은 하위 요소가 상위 요소의 이벤트를 인식하는 버블링의 원리이다.
* 오해할 수 있는 게, 버블링과 캡쳐링은 이벤트 동작의 방향을 말하는 것이 아니라 순서를 말하는 것이다. 예를 들어, 버블링은 하위에서 상위로의 이벤트 전달 방향을 말하는 게 아니라 하위 요소의 이벤트가 먼저 동작되고 상위 이벤트의 요소가 차후에 동작되는 이벤트의 순서를 말한다. (하위 요소가 상위 요소의 이벤트를 인식할 수 있다는 뜻으로 이해할 수 있음)
따라서, 상위 요소의 이벤트가 하위 요소에도 영향을 미치는 것은 캡쳐링의 원리가 아니라 버블링의 원리이다.
예를 들어, 이벤트가 있는 DOM을 추가하는 기능을 구현한다고 했을 때 매번 추가된 요소에 이벤트 리스너를 추가하는 것은 비효율적인 코드의 낭비일 수 있다. 이럴 때 이벤트 위임을 사용해서 그 DOM을 감싸고 있는 상위 요소에 이벤트 리스너를 준다면, 효율적으로 이벤트 기능을 추가할 수 있다. 즉, 이벤트 동작을 가지고 있는 형제 요소 혹은 자식 요소가 많을 때는 오히려 부모 요소에 이벤트 리스너를 주는 것이 효율적인 방법이다.
1. 버블링 : 하위 요소 -> 상위 요소
2. 캡쳐링 : 상위 요소 -> 하위 요소 (이벤트 리스너의 3번째 인자로 {capture: true}를 준다.)
3. 이벤트 위임 : 최상위 부모에게 이벤트 리스너를 줘서 e.target으로 자식 요소까지 모두 통제해버리기
4. 이벤트 버블링을 막고 싶을 때는 e.stopPropagation()
※ 이벤트 위임 요약 설명 : 상위 요소 1개와 하위 요소 n개가 있을 때, 하위 요소 하나하나에 이벤트 핸들러를 주는 게 아닌 상위 요소 1개에 이벤트 핸들러를 주고 event.target을 이용해 하위 요소를 제어할 수 있음. ☞ event.target으로 하위 요소를 감지할 수 있는 이유가 하위 요소의 이벤트가 상위 요소로 전파되는 이벤트 버블링을 사용하기 때문.
'JavaScript' 카테고리의 다른 글
[JavaScript] 배열의 값이 조건에 맞는지 확인하는 every(), some() (0) | 2023.02.09 |
---|---|
[JavaScript] 쿠키, 세션, 로컬 스토리지 비교하기 (0) | 2023.02.02 |
[JavaScript] 인코딩과 디코딩 (encodeURI, decodeURI) (0) | 2023.01.09 |
[JavaScript] new 생성자 함수로 유사한 객체 찍어내기 (0) | 2022.12.26 |
[JavaScript] 자바스크립트 엔진 동작 원리 (이벤트 루프) (0) | 2022.12.21 |