버블링과 캡처링

·2022년 6월 14일
0

신기한 개발 세상

목록 보기
9/12
post-thumbnail

아래 핸들러는 <div>에 할당되어 있지만, <em> 이나 <code>같은 중첩 태그를 클릭해도 동작한다.

<div onclick="alert('div에 할당한 핸들러!')">
  <em>EM</em>
</div>

왜 이런 일이 생길까?

이벤트 버블링(Event Bubbling)

한 요소에 이벤트가 발생하면, 이 요소에 할당된 handler가 동작하고 이어서 부모 요소 handler가 동작한다. 이게 최상단 요소를 만날 때까지 반복된다.

<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

여기서 <p>를 클릭하면 아래와 같이 동작한다.
1. <p>에 할당된 onclick handler가 작동
2. <div>에 할당된 handler가 작동
3. <form>에 할당된 handler가 작동
4. document 객체를 만날 때까지, 각 요소에 할당된 onclick handler가 동작

이런 동작 방식 때문에 <p> 요소를 클릭하면 p → div → form 순서로 3개의 alert 창이 뜨게된다.

이렇게 이벤트가 위로 점점 올라가는 흐름을 이벤트 버블링 이라고 부른다. 이벤트가 제일 깊숙한 곳에서 위로 올라가는 모양이 마치 물에서 숨쉬면 생기는 공기 방울과 닮았기 때문이다.
"거의" 모든 이벤트는 버블링된다 focus 이벤트와 같이 버블링 되지 않는 이벤트도 있다.

event.target & event.currentTarget

부모 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있다.

이벤트가 발생한 가장 안쪽의 요소는 타깃(target) 요소라고 불리고, event.target을 사용해 접근 가능하다.
우리는 event.target도 사용하지만 event.currentTarget(=this)을 사용할 때도 있다. 이 둘의 차이는 무엇일까?

  • event.target은 실제 이벤트가 시작된 ‘타깃’ 요소입니다. 버블링이 진행되어도 변하지 않는다.
  • event.currentTarget은 ‘현재’ 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조한다.
<form onclick="alert('form')">FORM
  <div onclick="alert('div')">DIV
    <p onclick="alert('p')">P</p>
  </div>
</form>

위 예시에선 아래와 같다.

클릭한 요소event.targetevent.currentTarget
formformform
divdivdiv -> form
ppp -> div -> form

버블링 중단하기

이벤트 버블링은 타깃 이벤트에서 시작해서 <html> 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생합니다. 몇몇 이벤트는 window 객체까지 거슬러 올라가기도 합니다. 이 때도 모든 핸들러가 호출된다.
그런데 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있다.
이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 된다.

하지만 꼭 필요한 경우를 제외하곤 버블링을 막지 않는게 좋다.
버블링은 유용하고 실제적으로 이벤트 버블링을 막아야 하는 경우는 거의 없다.

캡처링

이벤트엔 버블링 이외에도 ‘캡처링(capturing)’ 이라는 흐름이 존재한다.

표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계가 있다.

  1. 캡처링 단계 – 이벤트가 하위 요소로 전파되는 단계
  2. 타깃 단계 – 이벤트가 실제 타깃 요소에 전달되는 단계
  3. 버블링 단계 – 이벤트가 상위 요소로 전파되는 단계

테이블 안의 <td>를 클릭하면 아래와 같이 이벤트가 흐르게 된다.

<td>를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계), 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파된다(버블링 단계). 이런 과정을 통해 요소에 할당된 이벤트 핸들러가 호출된다.

캡처링 단계를 이용해야 하는 경우는 흔치 않기 때문에, 캡처링에 관한 코드를 발견하는 일은 거의 없을 것이다.

캡처링 단계에서 이벤트를 잡아내려면 addEventListener의 capture 옵션을 true로 설정해야 합니다.

elem.addEventListener(..., {capture: true}) 
/**
true이면 캡쳐링 단계에
false이면 버블링 단계에서 동작(default)
**/

공식적으론 총 3개의 이벤트 흐름이 있지만, 이벤트가 실제 타깃 요소에 전달되는 단계인 ‘타깃 단계’(두 번째 단계)는 별도로 처리되지 않는다. 캡처링과 버블링 단계의 핸들러는 타깃 단계에서 트리거된다.

<html>
  <body>
    <form>FORM
      <div>DIV
        <p>P</p>
      </div>
    </form>
  </body>
</html>

<p>를 클릭하면 다음과 같은 순서로 이벤트가 전달된다.

  1. HTML → BODY → FORM → DIV (캡처링 단계, 첫 번째 리스너)
  2. P (타깃 단계, 캡쳐링과 버블링 둘 다에 리스너를 설정했기 때문에 두 번 호출)
  3. DIV → FORM → BODY → HTML (버블링 단계, 두 번째 리스너)

캡처링 단계는 거의 쓰이지 않고, 주로 버블링 단계의 이벤트만 다뤄집니다. 이렇게 된 데는 논리적 배경이 있다.
특정 요소에 할당된 핸들러는 그 요소에 대한 자세한 사항과 무슨 일을 해야 할지 가장 잘 알고 있다. <td>에 할당된 핸들러는 <td>에 대한 모든 것을 알고 있기 때문에 <td>를 다루는데 가장 적합하다. 따라서 <td>를 다룰 기회를 이 요소에 할당된 핸들러에게 가장 먼저 주는 것이다.

참고자료

https://ko.javascript.info/bubbling-and-capturing#ref-2188

profile
이제는 병아리는 벗어나야하는 프론트개발자

0개의 댓글