WeniVooks

검색

JavaScript 베이스캠프

DOM의 이벤트 흐름

1. 이벤트 흐름

브라우저 화면에서 이벤트가 발생하면 브라우저는 가장 먼저 이벤트 대상을 찾기 시작합니다.

브라우저가 이벤트 대상을 찾아갈 때는 가장 상위의 window 객체부터 document, body 순으로 DOM 트리를 따라 내려갑니다. 이를 캡처링 단계라고 합니다.

이때 이벤트 대상을 찾아가는 과정에서 브라우저는 중간에 만나는 모든 캡처링 이벤트 리스너를 실행시킵니다. 그리고 이벤트 대상을 찾고 캡처링이 끝나면 이제 다시 DOM 트리를 따라 올라가며 만나는 모든 버블링 이벤트 리스너를 실행합니다. 이를 이벤트 버블링 단계라고 합니다.

그리고 이러한 과정에서 이벤트 리스너가 차례로 실행되는것을 이벤트 전파(event propagation)라고 합니다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="../reset.css" />
    <style></style>
  </head>
 
  <body>
    <article class="parent">
      <button class="btn" type="button">버튼</button>
    </article>
 
    <script>
      const parent = document.querySelector('.parent');
      const btnFirst = document.querySelector('.btn');
      btnFirst.addEventListener('click', (event) => {
        console.log('btn capture!');
      });
 
      window.addEventListener('click', () => {
          console.log('window capture!');
      }, true); // true : 캡처링 단계의 이벤트가 발생하도록 합니다.
 
      document.addEventListener('click', () => {
          console.log('document capture!');
      }, true);
 
      parent.addEventListener('click', () => {
          console.log('parent capture!');
      }, true);
 
      btnFirst.addEventListener('click', (event) => {
        console.log('btn bubble!');
      });
 
      parent.addEventListener('click', () => {
        console.log('parent bubble!');
      });
 
      document.addEventListener('click', () => {
        console.log('document bubble!');
      });
 
      window.addEventListener('click', () => {
        console.log('window bubble!');
      });
    </script>
  </body>
</html>
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="../reset.css" />
    <style></style>
  </head>
 
  <body>
    <article class="parent">
      <button class="btn" type="button">버튼</button>
    </article>
 
    <script>
      const parent = document.querySelector('.parent');
      const btnFirst = document.querySelector('.btn');
      btnFirst.addEventListener('click', (event) => {
        console.log('btn capture!');
      });
 
      window.addEventListener('click', () => {
          console.log('window capture!');
      }, true); // true : 캡처링 단계의 이벤트가 발생하도록 합니다.
 
      document.addEventListener('click', () => {
          console.log('document capture!');
      }, true);
 
      parent.addEventListener('click', () => {
          console.log('parent capture!');
      }, true);
 
      btnFirst.addEventListener('click', (event) => {
        console.log('btn bubble!');
      });
 
      parent.addEventListener('click', () => {
        console.log('parent bubble!');
      });
 
      document.addEventListener('click', () => {
        console.log('document bubble!');
      });
 
      window.addEventListener('click', () => {
        console.log('window bubble!');
      });
    </script>
  </body>
</html>

2. 이벤트 target, currentTarget

부모부터 자식까지 일련의 요소를 모두 타고가며 진행되는 이러한 이벤트의 특징 덕분에 이벤트 객체에는 target, currentTarget 이라는 속성이 존재합니다.

target 속성에는 이벤트가 발생한 진원지의 정보가 담겨 있습니다. target 속성을 통해 이벤트 리스너가 없는 요소의 이벤트가 발생했을 때도 해당 요소에 접근 할 수 있습니다.

currentTarget 속성에는 이벤트 리스너가 연결된 요소가 참조되어 있습니다.

<article class="parent">
  <ol>
    <li><button class="btn-first" type="button">버튼1</button></li>
    <li><button type="button">버튼2</button></li>
    <li><button type="button">버튼3</button></li>
  </ol>
</article>
 
<script>
  const parent = document.querySelector('.parent');
  parent.addEventListener('click', function (event) {
    console.log(event.target);
    console.log(event.currentTarget);
  });
</script>
<article class="parent">
  <ol>
    <li><button class="btn-first" type="button">버튼1</button></li>
    <li><button type="button">버튼2</button></li>
    <li><button type="button">버튼3</button></li>
  </ol>
</article>
 
<script>
  const parent = document.querySelector('.parent');
  parent.addEventListener('click', function (event) {
    console.log(event.target);
    console.log(event.currentTarget);
  });
</script>

3. preventDefault()

브라우저는 HTML 태그를 통해 여러가지 기능들을 제공합니다. 하지만 때때로 그러한 기능이 방해가 되는 경우가 있습니다. 이때 preventDefault()를 사용하여 브라우저의 기본 동작을 중지할 수 있습니다.

<!-- 앵커의 기본 동작을 중지 -->
<a href="https://www.naver.com" class="link">네이버 링크입니다만..</a>
<script>
  const link = document.querySelector('.link');
  link.addEventListener('click', (event) => {
    console.log('clicked');
    event.preventDefault();
  });
</script>
 
<!-- submit 의 기본 동작을 중지 -->
<form action="">
  <button type="submit" class="submit">제출</button>
</form>
<script>
  const submit = document.querySelector('.submit');
  submit.addEventListener('click', (event) => {
    console.log('clicked');
    event.preventDefault();
  });
</script>
<!-- 앵커의 기본 동작을 중지 -->
<a href="https://www.naver.com" class="link">네이버 링크입니다만..</a>
<script>
  const link = document.querySelector('.link');
  link.addEventListener('click', (event) => {
    console.log('clicked');
    event.preventDefault();
  });
</script>
 
<!-- submit 의 기본 동작을 중지 -->
<form action="">
  <button type="submit" class="submit">제출</button>
</form>
<script>
  const submit = document.querySelector('.submit');
  submit.addEventListener('click', (event) => {
    console.log('clicked');
    event.preventDefault();
  });
</script>

이렇듯 종종 브라우저의 기본 동작을 중지하고 자바스크립트를 통해 기능을 처리하고자 할때 사용합니다.

8.2 DOM 제어하기9장 설계 도면 Class