Jun 개발노트

stopPropagation vs stopImmediatePropagation

2021-10-11

0. 작성이유

  • 누군가 물어봤는데.. 대답을 잘하지 못했다.

  • stopImmediatePropagation의 역할에 대해서 궁금했다.

  • 예제 HTML

    <div id="one">
      <div id="two">
        <div id="three">
          AA
        </div>
        <a id="four" href="google.com">
    			BB
    		</a>
      </div>
    </div>
    

1. Event

  • DOM Event는 구독방식으로 작동한다.

    • 한 노드에 여러가지 이벤트를 등록 가능
    • 같은 Event 타입으로 여러가지의 핸들러를 등록 가능
    • 핸들러 작동 순서는 렉시컬 컨텍스트 순으로 작동된다.
  • 이벤트 등록 방식은 on[eventTyp], addEventListener(eventType, callback)

    • on방식은 이벤트가 쉐도잉 처리 되기때문에, 마지막에 할당한 핸들러만 작동

      • 쉐도잉은 on으로 등록된 이벤트만 처리되고, addEventListener는 정상 작동
    • addEventListener방식은 같은 이벤트에 여러 핸들러를 등록할 수 있다.

      • 해당 이벤트가 dispatch 되면, 등록된 핸들러가 다 작동된다.
      $four.addEventListener('click', (event) => {
          console.log('four addEventListener1')
        }, false)
      
      $four.addEventListener('click', (event) => {
          console.log('four addEventListener2')
        }, false)
      
      $four.onclick = ((event) => {
        console.log('four onclick1')
      })
      
      $four.onclick = ((event) => {
        event.preventDefault()
        console.log('four onclick2')
      })
      
      // four addEventListener1
      // four addEventListener2
      // four onclick2
      
  • 모던 브라우저의 기본 이벤트 타입은 버블링이다

    • 이벤트 리스너에서 처리하는게 버블링일뿐, 이벤트는 캡쳐링 → 버블링

    • addEventListner의 세번째 인자에 true로 바꾸면 캡쳐링

      $one.addEventListener('click', (event) => {
        console.log('one')
      }, true)
      $two.addEventListener('click', (event) => {
        console.log('two')
      }, false)
      $three.addEventListener('click', (event) => {
        console.log('three')
      }, false)
      
      // one
      // three
      // two
      

2. Event.preventDefault();

  • (<a>, <form>) 태그의 기본 이벤트(브라우저에 정의된)를 막는다.

    • <a id='four'> 기본 이벤트는 href에 등록된 주소로 페이지 이동
    • preventDefault 때문에 기본 기능을 하지 못하고 콘솔이 찍힌다.
    $four.addEventListener('click', (event) => {
      event.preventDefault();
      console.log('four')
      
    }, false)
    
    // four (not move to google.com)
    

3. Event.stopPropagation()

  • 브라우저의 이벤트 방식은 캡쳐링 → 버블링으로 이벤트 전파된다

    • document → body → target → body

      document.addEventListener('click', e => {
        console.log('document')
      }, true)
      
      document.addEventListener('click', e => {
        console.log('document')
      }, false)
      
      document.body.addEventListener('click', e => {
        console.log('body')
      }, true)
      
      document.body.addEventListener('click', e => {
        console.log('body')
      }, false)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, true)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, false)
      
      // document -> body -> target -> body -> document
      
  • 이러한 이벤트 전파 흐름을 막아준다

    • 캡쳐링 / 버블링 관계없이 이벤트의 전파 흐름을 막는다.

      document.addEventListener('click', e => {
        console.log('document')
      	e.stopPropagation()
      }, true)
      
      document.body.addEventListener('click', e => {
        console.log('body')
      }, false)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, true)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, false)
      
      // document(더 이상 이벤트가 발생하지 않는다)
      
      
    • 단, 해당 노드에 등록된 이벤트 핸들러들은 작동된다.

      document.addEventListener('click', e => {
        console.log('document1')
      	e.stopPropagation()
      }, true)
      
      document.addEventListener('click', e => {
        console.log('document2')
      }, true)
      
      document.body.addEventListener('click', e => {
        console.log('body')
      }, false)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, true)
      
      $one.addEventListener('click', (event) => {
        console.log('one')
      }, false)
      
      // document1
      // document2 (더 이상 이벤트가 발생하지 않는다)
      
      

4. Event.stopImmediatePropagation();

  • 해당 요소에 동일한 이벤트타입으로 등록된 핸들러의 전파를 멈춘다
    • 이벤트 핸들러는 등록 순서대로 작동된다(렉시컬 컨텍스트).

      $three.addEventListener('click', (event) => {
        console.log('three1')
      }, false)
      
      $three.addEventListener('click', (event) => {
        console.log('three2')
      }, false)
      
      // three1
      // three2
      
    • 순서 document → target(handler1, handler2 ..) → document순

      $three.addEventListener('click', (event) => {
        console.log('three1')
      }, false)
      
      $three.addEventListener('click', (event) => {
        console.log('three2')
        event.stopImmediatePropagation()
      }, false)
      
      $three.addEventListener('click', (event) => {
        console.log('three3')
      }, false)
      
      // three1
      // three2
      
      $three.addEventListener('click', (event) => {
        console.log('three1')
        event.stopImmediatePropagation()
      }, false)
      
      $three.addEventListener('click', (event) => {
        console.log('three2')
      }, false)
      
      $three.addEventListener('click', (event) => {
        console.log('three3')
      }, false)
      
      // three1
      

5. 특정 노드에 이벤트를 리셋시켜주고 싶을때는?

  • 관련 API는 찾을 수 없었고, 새롭게 노드를 만들어주는 방법뿐이다.

    elClone = $three.cloneNode(true);
    
    $three.parentNode.replaceChild(elClone, $three);
    

참고자료