이벤트 버블링

아래 코드의 이벤트는 div에 할당되어 있지만, em이나 code 같은 자식 태그를 클릭해도 동작합니다.

  <div onclick="console.log('click div');">
    <em><code>EM</code></em>을 클릭했는데도, <code>div</code>에 할당된 핸들러가 동작합니다.</em>
  </div>

버블링

한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다.
가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작합니다.

3개의 요소가 FORM > DIV > P 형태로 중첩된 구조에 각각 핸들러가 할당되어 있습니다.

  <form onclick="console.log('click form')">
    FORM
    <div onclick="console.log('click div')">
      DIV
      <br/>
      <p onclick="console.log(p)">P</p>
    </div>
  </form>

가장 안쪽의 p를 클릭하면 순서대로 다음과 같은 일이 벌어집니다.

  1. p에 할당된 onclick 핸들러 동작.
  2. div에 할당된 onclick 핸들러 동작.
  3. form에 할당된 onclick 핸들러 동작.
  4. document 객체를 만날 때까지, 각 요소에 할당된 onclick 핸들러 동작.

이런 동작 방식 때문에 p 요소를 클릭하면 p -> div -> form 순서로 3개의 콘솔 로그가 출력되는 것이조.
이런 흐름을 '이벤트 버블링'이라고 부릅니다.
이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 형세 입니다.

거의 모든 이벤트는 버블링 됩니다.
focus와 같이 버블링 되지 않는 이벤트도 있습니다.

event.target

부모 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있습니다.
이벤트가 발생한 가장 안쪽의 요소는 타깃(target) 요소라고 불리고, event.target을 사용해 접근할 수 있습니다.
event.target과 this (event.currentTarget)은 다음과 같은 차이가 있습니다.

  • event.target은 실제 이벤트가 시작된 '타켓'요소 입니다. 버블링이 진행되어도 변하지 않습니다.
  • this 는 현재 요소로, 현재 실행중인 핸들러가 할당된 요소를 참조합니다.

버블링 중단하기

이벤트 버블링은 타깃 이벤트에서 시작해서 html 요소를 거쳐 document 객체를 만날 때까지 각 노드에서 모두 발생합니다.
몇몇 이벤트는 window 객체까지 거슬러 올라가기도 합니다. 이 때도 모든 핸들러가 호출됩니다.

핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 할 수 있습니다.
event.stopPropagation()을 사용하면됩니다.

event.stopImmediatePropagation()

한 요소의 특정 이벤트를 처리하는 핸들러가 여러개인 상황에서, 핸들러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작합니다.
event.stopPropagation()은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못합니다.
버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면 event.stopImmediatePropagation()을 사용해야 합니다. 이 메소드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러들이 모두 동작하지 않습니다.

꼭 필요한 경우를 제외하곤 버블링을 막지 마세요

버블링은 유용합니다. 버블링을 꼭 멈춰야 하는 명백한 상황이 아니면 버블링을 막지 마세요. 아키텍쳐를 잘 고려해서 진짜 막아야 하는 상황에서만 버블링을 막으세요.
event.stopPropagation()은 추후에 문제가 될 수 있는 상황을 만들어 낼 수 있습니다.

  • 예시 시나리오
    1. 중쳡 메뉴를 만들었다 가정합니다. 각 서브메뉴에 해당하는 요소에서 클릭이벤트를 처리하도록 하고, 상위 메뉴의 클릭 이벤트 핸들러는 동작하지 않도록 stopPropagation을 적용합니다.
    2. 사람들이 페이지에서 어디를 클릭했는지 등의 행동 패턴을 분석하기 위해, window내에서 발생하는 클릭 이벤트 전부를 감지하기로 결정합니다. 일부 분석 시스템은 그렇게 분석합니다. 이런 분석 시스템의 코드는 클릭이벤트를 감지하기 위해 document.addEventListener('click', ...)을 사용합니다.
    3. stopPropagation으로 버블링을 막아놓은 영역에선 분석 시스템의 코드가 동작하지 않기 때문에 분석이 제대로 되지 않습니다. 안타깝게도 stopPropagation을 사용한 영역은 죽은 영역(dead zone)이 되어버립니다.

이벤트 버블링을 막아야 하는 경우는 거의 없습니다.
버블링을 막아야 해결 되는 문제라면 커스텀 이벤트 등을 사용해 문제를 해결할 수 있습니다.
핸들러의 event 객체에 데이터를 저장해 다른 핸들러에서 읽을 수 있게 하면, 아래쪽에서 무슨 일이 일어나는지를 부모 요소의 핸들러에게 전달할 수 있으므로, 이 방법으로도 이벤트 버블링을 통제할 수 있습니다.

 


참고 : https://ko.javascript.info/bubbling-and-capturing

728x90
반응형

'WEB > CLIENT_SIDE' 카테고리의 다른 글

Webpack과 Babel 그리고 Polyfill  (0) 2020.11.30

Webpack과 Babel 그리고 Polyfill

client side 개발을 위해 구글링 하다 보면, 자주 듣는 이름들이 있다.
깊은 설명은 못하고 간단하게 기록해 본다.

Webpack

웹팩은 의존성을 분석해 모듈들을 하나의 파일로 번들링 시켜주는 역할을 한다.
프로젝트를 개발하다 보면 client side 에서 많은 js 파일을 include 혹은 import 하게 되는데,
이런 파일들이 많으면 브라우져에서 렌더링하기 전까지 요청해야 하는 파일이 많아 지게 된다.
프로젝트의 구조를 분석하고, 자바스크립트 모듈과 css, image 등 여러 리소스들을 번들로 묶어 패킹하는
모듈 번들러(Module Bundler)이다.

Babel

바벨은 최신 JS 문법을 구형 브라우져(IE 라던지, IE 같은, IE들)에서도 작동할 수 있도록 코드 자체를 변환시키는 역할을 한다.
최신 문법을 구형 문법으로 변환 하는 일종의 compiler 혹은 transpiler.

바벨 테스트 하러 가기

Polyfill

폴리필은 js run-time에 필요한 기능을 주입하는 역할을 한다.
Babel이 코드를 구 브라우져에서 사용가능하게 변환하지만, ES5 이상에서 추가된 Promise, Map, Set과 같은 전역 객체나, Array.find, Array.filter 등 전역 객체에 추가된 메소드등을 실행 환경에서 제공하는 역할을 한다.


세가지 모두 npm 환경의 nodejs나 react에서 주로 사용하지만, Spring / jsp 환경에서도 위 기능을 필요한 경우가 있다.
이를 위해 Babel에서 browser.js 라는걸 지원 한적이 있고, 가장 최신은 bablel-standalone이 있는 것으로 알고 있다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"
integrity="sha512-kp7YHLxuJDJcOzStgd6vtpxr4ZU9kjn77e6dBsivSz+pUuAuMlE2UTdKB7jjsWT84qbS8kdCWHPETnP/ctrFsA=="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.12.1/polyfill.min.js"></script>
<script type="text/babel" data-presets="es2015">
  const a = (b) => {console.log( b );};
  a('asdf');

  const testTmplt = obj => {
    return `name : ${obj.name} age : ${obj.age}`;
  }
  
  const tmpRes = testTmplt({name:'asdf', age: 10});
  
  const ttTgt = document.getElementById('testTarget');
  ttTgt.innerText = tmpRes;
</script>
 

728x90
반응형

'WEB > CLIENT_SIDE' 카테고리의 다른 글

[FE] 이벤트 버블링 (Event Bubbling)  (0) 2023.07.14

+ Recent posts