항해/TIL

TIL(2/17) / JS 이벤트

yeeendy 2024. 2. 17. 16:34

이벤트

예를 들어 클릭했을 때 페이지가 넘어가는 등 이벤트가 발생하면 그에 맞는 반응을 해야 한다.

이를 위해 이벤트는 일반적으로 함수에 연결되며 그 함수는 이벤트가 발생하기 전에는 실행되지 않다가 이벤트가 발생되면 실행된다.

-> 이 함수를 이벤트 핸들러라고 한다.

 

이벤트 루프(Event Loop)

브라우저는 단일 스레드(single-thread)에서 이벤트 드리븐(event-driven) 방식으로 동작한다.

 

단일 스레드 : 스레드가 하나뿐이라는 의미, 곧 하나의 작업만을 처리할 수 있다

 

하지만 실제로 동작하는 웹 애플리케이션은 많은 task가 동시에 처리되는 것처럼 느껴진다.

이처럼 자바스크립트의 동시성(Concurrency)을 지원하는 것이 바로 이벤트 루프(Event Loop)다.

 

주요하게 생각할 점 call stack이 비어야 Callback queue(혹은 stack queue, event queue 다양하게 부른다)에 있는 함수가 콜스택으로 호출된다.

 

 

 

 

이벤트 핸들러

이벤트에 대응하는 처리하는 기술

 

이벤트 핸들러 등록 방법 3가지
  1. 인라인 이벤트 핸들러 방식
  2. 이벤트 핸들러 프로퍼티 방식
  3. addEventListener 메소드 방식  ✰✰✰

인라인 이벤트 핸들러 방식

HTML 요소의 이벤트 핸들러 어트리뷰트에 이벤트 핸들러를 등록하는 방법

✔︎ this는 전역 객체 window를 가리킨다

<!DOCTYPE html>
<html>
<body>
  // HTML 요소에 등록
  <button onclick="myHandler()">Click me</button>
  // 여러 개 전달 가능
  <!-- <button onclick="myHandler1(); myHandler2();">Click me</button> -->
  <script>
    function myHandler() {
      alert('Button clicked!');
    }
    /*
    function myHandler1() {
      alert('myHandler1');
    }
    function myHandler2() {
      alert('myHandler2');
    }
    */
  </script>
</body>
</html>

 

onclick 같이 on으로 시작하는 이벤트 어트리뷰트의 값으로 함수 호출을 전달

이때 이벤트 어트리뷰트의 값으로 전달한 함수 호출이 즉시 호출되는 것은 아니다.

 

하지만 이 방식은 사용하지 않아야 한다.

-> HTML과 JavaScript는 관심사가 다르므로 분리하는 것이 좋음

 

이벤트 핸들러 프로퍼티 방식

인라인 이벤트 핸들러 방식같이 HTML과 JavaScript가 뒤섞이는 문제를 해결할 수 있는 방식

하지만 이벤트 핸들러 프로퍼티에 하나의 이벤트 핸들러만 바인딩할 수 있다.

✔︎ this는 이벤트에 바인딩된 요소를 가리킨다 (이벤트 객체의 currentTarget 프로퍼티와 동일)

<!DOCTYPE html>
<html>
<body>
  <button class="btn">Click me</button>
  <script>
    const btn = document.querySelector('.btn');

    // 이벤트 핸들러 프로퍼티 방식은 이벤트에 하나의 이벤트 핸들러만을 바인딩할 수 있다
    // 첫번째 바인딩된 이벤트 핸들러 => 실행되지 않는다.
    btn.onclick = function () {
      alert('① Button clicked 1');
    };

    // 두번째 바인딩된 이벤트 핸들러
    // 실행됨
    btn.onclick = function () {
      alert('① Button clicked 2'); // 첫 번째 실행
    };

    // addEventListener 메소드 방식
    // 첫번째 바인딩된 이벤트 핸들러
    btn.addEventListener('click', function () {
      alert('② Button clicked 1'); // 두 번째 실행
    });

    // 두번째 바인딩된 이벤트 핸들러
    btn.addEventListener('click', function () {
      alert('② Button clicked 2'); // 세 번째 실행
    });
  </script>
</body>
</html>

 

addEventListener 메소드 방식

addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.

✔︎ this는 이벤트 리스너에 바인딩된 요소(currentTarget)를 가리킨다 (이벤트 객체의 currentTarget 프로퍼티와 동일)

장점

  • 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있다.
  • 캡처링과 버블링을 지원
  • HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작한다.
    브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.
<!DOCTYPE html>
<html>
<body>
  <script>
    // 대상 DOM 요소를 지정하지 않으면 전역객체에서 발생하는 click이벤트에 이벤트 핸들러를 바인딩
    // 어딜 클릭해도 이벤트 핸들러가 동작
    addEventListener('click', function () {
      alert('Clicked!');
    });
  </script>
</body>
</html>

 

이벤트의 흐름

계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다.

이벤트 전파 방향에 따라 버블링 캡처링으로 구분할 수 있다.

  • 버블링 : 자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것
  • 캡처링 : 자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것
  • 주의점❗️ 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료한다

예시로 살펴보자

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    body area
    <main>
      main area
      <div>
        div area
        <p>
          p area
          <span> span area </span>
        </p>
      </div>
    </main>
    <script>
      const $body = document.querySelector("body");
      const $main = document.querySelector("main");
      const $div = document.querySelector("div");
      const $p = document.querySelector("p");
      const $span = document.querySelector("span");

      //bubbling
      $span.addEventListener("click", function () {
        console.log(`span 태그`);
      });
      $p.addEventListener("click", function () {
        console.log(`p 태그`);
      });
      $div.addEventListener("click", function () {
        console.log(`div 태그`);
      });
      $main.addEventListener("click", function () {
        console.log(`main 태그`);
      });
      $body.addEventListener("click", function () {
        console.log(`body 태그`);
      });
    </script>
  </body>
</html>

span 태그를 클릭했음에도 버블링으로 올라가는 것을 확인할 수 있다.

addEventListener는 기본적으로 버블링으로 동작

 

캡처링이 되도록 작성하려면 어떻게 해야할까?

-> addEventListener 3번째 인자true로 주면 캡처링(default는 false이고 버블링을 의미한다)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    body area
    <main>
      main area
      <div>
        div area
        <p>
          p area
          <span> span area </span>
        </p>
      </div>
    </main>
    <script>
      const $body = document.querySelector("body");
      const $main = document.querySelector("main");
      const $div = document.querySelector("div");
      const $p = document.querySelector("p");
      const $span = document.querySelector("span");
      
      //capturing
      $span.addEventListener("click", function () {
          console.log(`capturing span 태그`);
        },true);
      $p.addEventListener("click", function () {
          console.log(`capturing p 태그`);
        },true);
      $div.addEventListener("click", function () {
          console.log(`capturing div 태그`);
        },true);
      $main.addEventListener("click", function () {
          console.log(`capturing main 태그`);
        },true);
      $body.addEventListener("click", function () {
          console.log(`capturing body 태그`);
        },true);

      //bubbling
      $span.addEventListener("click", function () {
        // event.stopPropagation();
        console.log(`span 태그`);
      });
      $p.addEventListener("click", function () {
        console.log(`p 태그`);
      });
      $div.addEventListener("click", function () {
        console.log(`div 태그`);
      });
      $main.addEventListener("click", function () {
        console.log(`main 태그`);
      });
      $body.addEventListener("click", function () {
        console.log(`body 태그`);
      });
    </script>
  </body>
</html>

addEventListener 3번째 인자로 true를 작성하면 capturing 감지

span을 클릭했을 때 캡쳐링이 일어나는 것을 확인할 수 있다.

 

Event 객체

event 객체는 이벤트를 발생시킨 요소와 발생한 이벤트에 대한 유용한 정보를 제공한다.

이벤트가 발생하면 event 객체는 동적으로 생성되며 이벤트를 처리할 수 있는 이벤트 핸들러에 인자로 전달된다.

이벤트 핸들러를 선언할 때, event 객체를 전달받을 첫번째 매개변수를 명시적으로 선언해야 한다.

 

Event Property

✔︎ Event.target

=> 실제로 이벤트를 발생시킨 요소를 가리킨다

✔︎ Event.currentTarget

=> 이벤트에 바인딩된 DOM 요소를 가리킨다. (즉, addEventListener 앞에 기술된 객체를 의미)

✔︎ Event.type

=> 발생한 이벤트의 종류를 나타내는 문자열을 반환한다.

✔︎ Event.cancelable

=> 요소의 기본 동작을 취소시킬 수 있는지 여부(true / false)를 나타낸다.

✔︎ Event.eventPhase

=> 이벤트 흐름(event flow) 상에서 어느 단계(event phase)에 있는지를 반환한다.

반환값 의미
0 이벤트 없음
1 캡쳐링 단계
2 타깃
3 버블링 단계

 

기본 동작의 변경

이벤트 객체는 요소의 기본 동작과 요소의 부모 요소들이 이벤트에 대응하는 방법을 변경하기 위한 메소드를 가지고 있다.

✔︎ Event.preventDefault()

=> 요소가 가지고 있는 기본 동작을 중단시키기 위한 메소드

✔︎ Event.stopPropagation()

=> 어느 한 요소를 이용하여 이벤트를 처리한 후 이벤트가 부모 요소로 이벤트가 전파되는 것을 중단시키기 위한 메소드

=> 부모 요소에 동일한 이벤트에 대한 다른 핸들러가 지정되어 있을 경우 사용된다.

'항해 > TIL' 카테고리의 다른 글

TIL(2/23) / HTTP  (0) 2024.02.23
TIL(2/19) / 프로토타입  (0) 2024.02.19
TIL(2/15) / 객체, 변경불가성, 빌트인 객체  (0) 2024.02.15
TIL(2/13) / JS 메서드  (0) 2024.02.13
TIL(2/10) / DOM, 클래스, 클로저  (0) 2024.02.10