본문 바로가기
JavaScript

JavaScript] Event Bubbling and capturing

by Fastlane 2022. 11. 23.
728x90
반응형

출처 : https://javascript.info/bubbling-and-capturing

<div onclick="alert('The handler!')">
  <em>If you click on <code>EM</code>, the handler on <code>DIV</code> runs.</em>
</div>

handler는 <div>에 할당 되었지만, 내부의 <em> 또는 <code> 어느 것을 클릭해도 동일하게 실행된다. 

Bubbling

bubbling 원칙은 단순하다. 

한 element에 event 발생 시, 맨처음 element에 있는 handler가 실행되고 그 다음 부모 그 다음 부모로 실행된다. 

FORM > DIV > P로 중첩된 elements가 있고, 각자 handler가 있다. 

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

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

p를 클리갛면 p -> div -> form 순으로 alert을 볼 수 있다. 

 

대부분의 event는 bubble이다. 예외로 focus event는 bubble이 아니다. 

event.target

event를 발생시키는, 대부분의 중첩된 element는 target element라고 불리며 event.target으로 접근이 가능하다. 

this와의 차이점은 다음과 같다. 

  • event.target - event를 초기화한 target element이며 bubbling진행 중 변경되지 않는다. 
  • this - current element로, 현재 handler를 실행중인 element이다. 

예를들어, form.onclick이라는 single handler가 있다. form내부의 어떤 걸 클릭해도 handler가 실행된다. 

form.onclick handler:

  • this (event.currentTarget) 은 <form> element이다. 
  • event.target 은 form안에 실재로 클릭된 element이다. 

<form> element를 클릭하면 event.target과 this는 같다. 

Stopping bubbling

bubbling event는 target element로부터 올라가, <html>까지 올라간다. 그다음 document object 어떤 event는 window까지 접근한다. 

어느 handler는 event가 다 실행되었고 bubbling을 멈추어야 할때가 있다. 

event.stopPropagation()함수를 사용한다. 

<body onclick="alert(`the bubbling doesn't reach here`)">
  <button onclick="event.stopPropagation()">Click me</button>
</body>

<button>을 클릭해도 body.onclick이 실행되지 않는다. 

event.stopPropagation()은 현재 element가 아닌 부모로부터 실행되는 handler를 정지한다. 

 

event.stopImmediatePropagation()

element에 하나의 event에 대한 여러개의 handler가 있을 때, 멈추고 싶다면 event.stopImmediatePropagation()함수를 호출해야 한다. 이후 다른 handler는 실행되지 않는다.

 

Don't stop bubbling without a need!

필요한 경우가 아니면 event.stopPropagation()을 사용하지 말자. 아래와 같은 숨겨진 리스크가 있다.

  1. nested menu를 만들고, 각 submenu가 click을 handle하고 stopPropagation을 호출하면 바깥 menu가 trigger하지 않는다. 
  2. 사이트 분석을 위해 전체 window에 대한 click을 catch하기로 할때, document.addEventListener('click')을 주로 사용한다. 
  3. stopPropagation으로 event가 중지되면 분석에 대한 deadzone을 갖게된다. 

Capturing

capturing이라고 하는 또 다른 형태의 event 처리방식이 있다. 실제 코드에서 드물에 사용하지만 때때로 유용하다. 

 

표준 DOM Events는 3가지 형태의 event propagation을 설명한다. 

  1. Capturing phase - event가 element로 내려간다. 
  2. Target phase - event가 target element로 접근한다. 
  3. Bubbling phase - event가 element로 부터 올라간다. 

<td>의 클릭에 대한 event는 첫번째로 Window에서 시작해 1번을 통해서 2번 element로 가고 target에 도착헤 trigger한 다음 3번으로 올라간다. 

 

사실 capturing은 보이지 않는다. 왜냐하면 on<event> 또는 addEventListener(event, handler)는 2번과 3번에 대해서만 실행한다. 

 

capturing phase에 대한 event를 catch하기 위해서 handler에 capture option을 true로 설정해야 한다. 

elem.addEventListener(..., {capture: true})
//삭제시에도 true가 필요하다. elem.removeEventListener(..., true)
// or, just "true" is an alias to {capture: true}
elem.addEventListener(..., true)

capture option에 대한 두가지 가능한 값이 있다. 

  • false(default) : handler는 bubbling phase에 대해 세팅한다. 
  • true : handler는 capturing phase에 대해 세팅한다. 

2번째 phase는 별도로 handle되지 않는다. 1번, 2번 phase에 의해 실행된다. 

<style>
  body * {
    margin: 10px;
    border: 1px solid blue;
  }
</style>

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

<script>
  for(let elem of document.querySelectorAll('*')) {
    elem.addEventListener("click", e => alert(`Capturing: ${elem.tagName}`), true);
    elem.addEventListener("click", e => alert(`Bubbling: ${elem.tagName}`));
  }
</script>

<p>를 클릭하면 

  1. capturing phase : HTML -> BODY -> FORM -> DIV -> P
  2. bubbling phase : P -> DIV -> FORM -> BODY -> HTML

 P는 capturing, bubbling 2번 보여진다. 

 

event.eventPhase property는 phase의 숫자를 알려준다. 

capturing되는 동안 event.stopPropagation()을 실행하면 bubbling이 발생하지 않는다. 

728x90
반응형

댓글