Event 파헤쳐 보기

밍글·2025년 2월 1일
1

FE스터디

목록 보기
2/4

시작하기 전에

해당 글은 1/12에 노션으로 작성한 걸 2부로 나눈 것입니다. 다음 포스팅은 Debounce, Throttle, 이벤트 시뮬레이션을 다룰 것입니다.

Event

  • 웹페이지에서 마우스를 클릭했을 , 키를 입력했을 , 특정요소에 포커스가 이동돼었을 어떤 사건을 발생시키는 것

  • 대부분 상호작용이 되는 곳은 전부 이벤트가 활용된다.

  • 기본적인 이벤트 종류 (자세한 내용은 아래 사이트에서 쉽게 확인할 수 있다.)
    이벤트 종류 확인하기

    - UI 이벤트 : load, scroll…
    - 키보드 이벤트 : keydown, keyup
    - 마우스 이벤트 : click, mouseover, mousedown…
    - 폼 요소 이벤트 : submit, focus…
    - CSS 이벤트 : transitioned

Event 객체

이벤트가 발생하면 브라우저는 이벤트 객체라는 것을 만든다. 여기에 이벤트에 관한 상세한 정보를 넣은 다음, 핸들러에 인수 형태로 전달한다.

  • event.target : 이벤트가 발생한 요소 (사용자가 의도한 가장 명확한 요소)
  • event.currentTarget : 이벤트에 바인딩된 DOM요소를 가리킨다. 즉, addEventListener 앞에 기술된 객체를 가리키는 것
  • event.type : 발생한 이벤트 종류
  • event.cancelable : 기본 동작을 취소시킬 수 있는 여부
  • event.key : 키보드를 친 경우 어떤 키를 쳤는지 알 수 있는 요소
  • event.eventPhase : 이벤트 흐름 상에서 어느 단계에 있는지 반환
    eventPhase사진
  • event.clientX / event.clientY : 포인터 관련 이벤트, 커서의 상대 좌표

-> 여기서 좌표는 모니터 기준 좌표가 아니라 브라우저 화면 기준 좌표이다!

Event handler

  • 이벤트에 반응하려면 이벤트가 발생했을 때 실행되는 함수인 핸들러를 할당해야 한다.
  • 핸들러는 사용자의 행동에 어떻게 반응할지를 JS코드로 표현한 것 → Event Listener라고도 볼 수 있다.
  • 핸들러는 여러 가지 방법으로 할당할 수 있으며 다음과 같이 있다.
    • HTML안에 on[event] 속성에 할당하기 ex ) button
    • DOM 프로퍼티에 on[event]을 사용하기
      - 대상 DOM 요소에 on 접두사를 붙인 이벤트명으로 이벤트 핸들러 프로퍼티를 설정하고 이벤트 발생시 처리할 함수를 등록하기
      • addEventListener() 메소드 활용하기
        • addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.
          addEventListener사진
        • 이 방식은 위의 두 방식과 달리 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있고 뒤에 서술할 캡처링과 버블링을 지원한다. 마지막으로 HTML 요소뿐만 아니라 모든 DOM요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다.

❓ 코드 차이

결론만 말하면 button.addEventListener('click', function() { handleClick(); }); 코드와 button.addEventListener('click', handleClick); 코드의 차이점이 뭘까?

  1. 익명함수 사용유무
    a. 전자는 익명 함수를 전달하고 있기 때문에 이벤트가 발생할 때마다 새로운 함수 객체가 생성된다.
  2. 기명함수 직접 전달
    a. 후자는 기명함수를 직접 전달하기 때문에 이벤트 리스너 등록 시 한 번만 함수 객체가 생성되고, 이후 이벤트 발생 시에는 기존 함수 객체를 재사용한다.
  3. 메모리 관리 차이
    a. 전자는 새로운 함수 객체를 생성하므로 불필요한 메모리 할당과 해제가 반복적으로 일어나고 이는 메모리 누수의 원인이 될 수 있다.
    b. 후자는 해당 객체를 재사용하므로 메모리 관리가 효율적이다.
  4. 가비지 컬렉션 차이
    a. 전자는 이벤트 리스너가 제거되기 전까지 가비지 컬렉션의 대상이 되지 않는다.
    b. 후자는 함수 객체가 전역 객체에 있으므로 가비지 컬렉션의 대상이 되기 쉽다. → 즉 제대로 제거하지 않으면 메모리 누수의 원인이 될 수 있다. 하지만 그럼에도 메모리 관리가 효율적이기 때문에 이게 더 성능 최적화에 적합하다.

Event 성능 최적화

페이지에 존재하는 이벤트 핸들러의 개수가 페이지 성능에 직접적인 영향을 미친다.
가령 input에 계속 동작한다던지, scroll 이벤트, resize 이벤트마다 발생하는 핸들러가 여러 개라면? omg..

  • 원인
    • 각 함수가 메모리를 점유하는 객체이기 때문이다. (함수가 객체라는 건 다른 포스팅에서 봤죠?) 즉 메모리를 많이 사용할수록 성능은 떨어질 수 밖에 없다.
    • 이벤트 핸들러를 많이 할당하려면 DOM 접근도 많아지고 이는 전체 페이지의 응답성을 떨어트리게 된다.
  • 개선 방법
    • 이벤트 위임 - 이벤트 핸들러 개수 줄이기 → 이는 뒤에 후술할 것
    • 더 이상 필요하지 않은 이벤트 핸들러 제거하기
      • 문서에서 요소를 제거하거나 페이지를 떠나는 경우, 요소는 제거되지만 이벤트 핸들러는 남아 메모리를 점유한다. 요소를 제거할 것이라면 잔류 핸들러를 직접 제거하는 것이 좋고 그러기 위해서는 떠나기 전에 onunload 이벤트 핸들러를 이용하여 잔류 핸들러를 모두 제거하는 것이 좋다.

      • 단, onunload 이벤트 핸들러를 사용하면 페이지가 bfcache에 저장되지는 않으므로 잘 선택해야 한다.

        💡 bfcache?
        back/forward cache의 약자로 이전/다음으로 이동할 때, 페이지 전체를 캐싱하여 페이지를 로드하는 시간과 데이터를 절약하는 기법

Event 전파

위에서 설명했다시피 계층적 구조에 포함되어 있는 HTML요소에 이벤트가 발생할 경우 연쇄적 반응이 일어나는 것

개념설명

Bubbling

자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 것
즉, 특정 화면 요소에서 이벤트가 발생하면 해당 이벤트가 상위의 화면 요소들로 전달되어 가는 특성을 가진다.

Capturing

자식 요소에서 발생한 이벤트가 부모 요소부터 시작하여 이벤트를 발생시킨 자식 요소까지 도달하는 것
즉, 이벤트가 발생했을 때 해당 이벤트가 최상위 요소인 body부터 해당 이벤트가 발생한 태그까지 전달되며 내려가는 특징을 가진다.

✅ 두 가지 전파 방식은 동시에 일어날까?

일반적으로 이벤트는 후술할 흐름에서 보면 알겠지만 캡처링 → 버블링 방식으로 전파되지만 대부분의 경우 우리는 버블링 단계에서 이벤트를 처리한다.

🔥 이벤트 핸들러 등록 방식에 따른 전파 차이

이벤트 핸들러 어트리뷰트, 프로퍼티 방식 : 이 방식을 이용한 경우 버블링 단계의 이벤트만 캐치할 수 있다.

addEventListener 이용 방식 : 모든 단계의 이벤트를 캐치할 수 있고, 3번째 매개변수는 캡처링 이벤트를 캡처링할 건지 결정하는데 쓰인다. 기본은 false지만 true일 경우 캡처링 단계의 이벤트를 캐치할 수 있다.

흐름

계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파되는데 전파 방향에 따라 버블링(Event Bubbling)과 캡처링(Event Capturing)으로 구분할 수 있다.  주의할 것은 버블링과 캡처링은 둘 중에 하나만 발생하는 것이 아니라 캡처링부터 시작하여 버블링으로 종료한다는 것이다. 즉, 이벤트가 발생했을 때 캡처링과 버블링은 순차적으로 발생한다. 흐름을 보면 다음과 같다.

흐름도

  1. 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계
  2. 타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계 → 리스너 실행
  3. 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계

전파가 왜 있을까? 논리적으로는 자식 요소가 부모 요소 영역 안에 위치하고 있기 때문에 자식 요소만을 클릭했다 하더라도 부모 요소도 클릭했다고 넓은 영역에서 보면 맞는 말이기 때문… (그 A는 B이고 B는 C면 A는 C이다 같이…), 이벤트 위임과 같은 기법을 사용하면 이벤트를 일일이 등록하지 않아도 되므로 성능적으로 좋기 때문

전파 방지 방법

만일 부모와 자식 둘 다 이벤트를 등록한 상태에서, 자식 요소만 클릭했을 때만 이벤트 발생하고 부모 요소는 이벤트를 발생시키고 싶지 않은 상황에서 이벤트 동작 자체를 바꿀 수 없으므로 이벤트 전파를 방지 처리를 하는 식으로 해결해야 한다.

전파 방지는 필요한 경우가 아니면 죽은 영역이 될 수 있는 위험이 있으므로 아키텍처를 잘 고려해서 사용해야 한다.

e.stopPropagation()

버블링 또는 캡처링 설정에 따라 상위, 하위로 가는 이벤트 전파를 막을 수 있다. 즉, 각 엘리먼트의 이벤트 리스너만 동작할 수 있게 해준다.

e.stopImmediatePropagation()

이벤트 전파와 더불어 형제 이벤트 실행을 중지한다. 동일한 child 요소의 이벤트 리스너가 2개 등록 되어 있을 때, 어떠한 조건에서 클릭 이벤트를 두 번 실행하지 않고 한 번만 실행토록 하길 원한다면 유용하다.

위의 e.stopPropagation() 은 그 함수 자체의 전파는 막아주지만 다른 형제 핸들러들이 동작하는 건 막지 못하므로 요소에 할당된 다른 핸들러의 동작도 막으려면 해당 함수를 쓰는 것이 좋다.

e.target으로 조건 걸어 방지

정교하게 하고 싶을 때 직접 조건 분기를 통해 일일이 지정해 주는 방식이다.

e.preventDefault()

이벤트 전파 , 형제 이벤트 실행 중지 뿐만 아니라 기본 이벤트 동작 자체를 취소한다.

Event 위임

다수의 자식 요소에 각각 이벤트 핸들러를 바인딩하는 대신 하나의 부모 요소에 이벤트 핸들러를 바인딩하는 방법, DOM 트리에 새로운 자식 태그 요소를 추가하더라도 이벤트 처리는 부모 요소인 태그 요소에 위임되었기 때문에 새로운 요소에 이벤트를 핸들러를 다시 바인딩할 필요가 없다.

왜 사용해야 할까?

  • 하나의 이벤트 핸들러를 사용하여 여러 이벤트를 효율적으로 처리할 수 있다고 한다.
    • 이는 결국 전체 페이지의 이벤트 유형을 효율적으로 관리하는 데 도움이 된다.

이벤트 위임 사진

  • 관리할 기능 자체가 줄어들기도 하고 사용 메모리 축소, DOM을 다루는 코드가 적어진다는 장점을 가지게 된다. 또한 동적으로 추가되는 요소에도 이벤트를 자동으로 적용 가능한 동적 요소 처리에도 유용하다.
🚨 주의할 점
  1. 성능 : 기본적으론 좋아지지만 너무 많은 요소에 이벤트를 위임하면 오히려 저하를 발생할 수 있다.
  2. 특정 요소만 처리 : event.target을 이용하여 원하는 요소만 처리해야 한다.
  3. 이벤트 버블링 이해 : 이를 정확히 이해해야 의도하지 않은 이벤트 처리가 발생하지 않는다.
  4. 컵처링 단계 활용 : 필요에 따라 캡처링 단계를 활용할 수 있지만, 일반적으로 버블링 단계를 사용하는 것이 더 일반적이다.
  • React와 Vue.js와 같은 곳에서 각 이벤트 처리 방식이 다를 수 있지만 이벤트 위임의 기본적인 개념은 동일하게 적용된다.
    • React

      JSX를 사용하여 이벤트 핸들러를 직접 작성하고, event.target을 이용하여 이벤트 처리한다.

    • Vue.js

      v-on 지시어를 사용하여 이벤트 핸들러를 등록하고, $event 객체를 통해 이벤트 정보에 접근한다.


참고자료

PoiemaWeb

이벤트 입문 - Web 개발 학습하기 | MDN

🌐 한눈에 이해하는 이벤트 흐름 제어 (버블링 & 캡처링)

Javascript 이벤트 전파와 이벤트 위임

profile
예비 초보 개발자의 기록일지

0개의 댓글

관련 채용 정보