Event 파헤쳐 보기 - 2부

밍글·5일 전
0

FE스터디

목록 보기
4/4

시작하기 전에

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

Debounce, Throttle

여기서는 간단히 다루도록 하겠다. 아예 안 다루기에는 이벤트 최적화 방법 중 하나라 애매하기 때문이다.

Debounce

  • 연속적으로 발생한 이벤트를 하나로 처리하는 방식이다.
  • 주로 처음이나 마지막으로 실행된 함수만을 실행한다.

Debounce

  • 동작원리
    • 쉽게 말해 계속해서 이벤트가 발생할 때 앞선 타이머를 리셋하고 타이핑을 멈추고나서 setTimeout으로 설정된 특정 시간이 지나면 딱 한번만 함수가 동작하는 방식이라고 보면 된다.
  • 주로 사용처 : 키워드 검색 혹은 자동완성 기능에서 api 함수 호출 횟수를 최대한 줄이고 싶을 때, 사용자가 창크기 조정을 멈출때까지 기다렸다가 resizing Event를 반영하고 싶을 때

Throttle

  • 출력을 조절한다는 의미로 이벤트를 일정주기마다 발생하도록 하는 기법
  • 100ms를 준다면 이벤트는 100ms동안 최대 한 번만 발생하게 된다.
  • 즉 마지막 함수가 호출된 후 일정시간이 지나기전에 다시 호출되지 않도록 하는 것이다.
    • 연이어 발생한 이벤트에 대해 일정한 delay를 포함시켜 연속적으로 발생한 이벤트는 무시하는 방식이다.
  • 동작원리
    • 이것도 일종의 setTimeout기법인데 첫 번째 이벤트가 발생한 후 내가 설정한 시간 동안 발생한 해당 이벤트들은 전부 무시된다. 결과적으로 500ms라고 가정한다면 그 시간 동안 최대 1번의 이벤트만이 발생하는 것이다.(마지막과는 상관이 없는 것이다)
  • 주로 사용처 : 스크롤, lodash라이브러리

💡 문제점

다만 throttle, debounce 함수의 단점이 존재한다.
동작원리를 보면 알다시피 둘 다 setTimeout이라는 WebAPI에 의해 실행되는데 setTimeout, setInterval은 정확한 지연 시간을 보장해주지 않는다.
저번에 비동기 API Tasks들을 Task Queue에 넣어둔 후 순차적으로 처리한다는 것을 보았었는데 Queue에 저장된 비동기 태스크를 처리하는 시점은 Call Stack이 비어져 있을 경우이다.
→ 이 때 시점이 setTimeout 또는 setInterval에 할당해준 delay와 맞지 않는 경우 등록해둔 callback은 실행되지 않을 수도 있다. 즉, 정교함은 약간 떨어질 수 있다는 것이다.

해결 방안들

Lodash 사용

lodash에서는 array 자료형 method 뿐 아니라 throttle과 debounce 개념이 적용되어 있고 이것들을 간단하게 사용할 수 있는 라이브러리이다. **기존의 debounce, throttle에 비해 시간 계산과 실행 관리를 더 정교하게 할 수 있다.**

debounce 내부 코드를 뜯어보면 기존과 차이점이 다음과 같다.

  1. 입력이 들어오면 현재 시간과 마지막 호출 그리고, delayTime (wait) 을 비교하고, lastArgs, lastThis, lastCallTime 를 저장한다.
    • lastArgs, lastThis를 저장해서 함수 실행 컨텍스트를 정확하게 유지한다.
    • lastCallTime을 저장하고 계속 추적함으로써 실제 호출 간격을 정확하게 측정한다.
  2. timerId 가 세팅되지 않았다면, leadingEdge 를 호출하여, startTimer(timerExpired, wait) 로 timerId 를 세팅한다.
    • leadingEdge와 뒤에 후술할 trailingEdge로 실행 시점을 명확하게 구분하여 콜백 실행 시점을 더 정확하게 제어가 가능하다.
  3. wait 시간이 지나면 timerExpired 로 마지막 입력 시간 (lastCallTime) 을 비교하여, 만약 입력이 계속 들어오고 있다고 판단되면, timerId = startTimer(timerExpired, remainingWait(time)) 반복한다.
    • timerExpired에서 입력이 계속 들어오는지 확인하고 remainingWait로 타이머를 다시 설정한다.
    • remainingWait로 남은 시간을 동적으로 계산해 타이밍 정확도를 향상시킨다.
  4. 만약 더이상 입력이 없고, 시간이 지났다면, trailingEdge(time) 를 호출하여, invokeFunc() 에서, func.apply(thisArg, args) 으로 callback 을 전달하게 한다.

requestAnimationFrame

requestAnimationFrame은 브라우저의 화면 갱신 주기에 맞추어 함수를 호출하는 방법으로, 화면이 새로 그려질 때(Repaint)마다 함수가 실행한다.
즉, 실제 화면이 갱신되어 표시되는 주기에 따라 함수를 호출해주기 때문에 자바스크립트가 프레임 시작 시 실행되도록 보장한다.
requestAnimationFrame을 이용해 쓰로틀링 적용하고 repaint를 개선한 사례에 대한 글이다.
scroll event 최적화로 웹페이지 성능 개선하기

Intersection Observer API

스크롤 이벤트 관리 시 해당 API를 사용하면 해결할 수 있다.

Intersection Observer API는 상위 요소 또는 최상위 문서의 viewport와 대상 요소 사이의 변화를 비동기적으로 관찰할 수 있는 수단을 제공한다.
Intersection Observer API는 특정 요소가 다른 요소와의 교차점에 들어가거나 나갈 때 또는 두 요소 간의 교차점이 지정된 양만큼 변화될 때 실행되는 콜백 함수를 코드에 등록할 수 있다.

즉, 기존 scroll 이벤트의 문제점을 개선하고자 개발되었다.

해당 부분의 자세한 부분은 여기서 확인할 수 있다.
실무에서 느낀 점을 곁들인 Intersection Observer API 정리

결국 Intersection Observer를 이용한다는 것은 WebAPI에 이벤트를 위임하는 것이고, 따라서 자연스럽게 메인 스레드의 자유도도 보장한다고 볼 수 있다.

이벤트 시뮬레이션

JS로 언제든 원하는 이벤트를 발생시킬 수 있다. 이 기능은 웹 애플리케이션을 테스트할 때 유용하다.

✨ 왜 유용할까?

  1. 테스트 자동화
    • 실제로 마우스를 클릭하거나 키보드를 누르지 않고도 테스트를 자동으로 실행할 수 있다.
    • ex ) 버튼 클릭 시 팝업 띄우기를 실제 클릭 없이 테스트가 가능하다.
  2. 다양한 상황 테스트가 가능하다.
    • 사용자가 하기 어려운 복잡한 이벤트 조합을 쉽게 테스트할 수 있다.
    • ex ) 마우스를 특정 좌표로 이동하면서 동시에 키를 누르기

createEvent 방식

document의 createEvent 메서드로 event 객체를 생성하고, 이벤트에 관한 정보를 초기화한 후, dispatchEvent 메서드로 이벤트를 발생시킨다.

// 예시
// 이벤트 객체 생성
var event = document.createEvent("MouseEvent");

// 이벤트 초기화 (세부 정보 설정)
event.initMouseEvent(
    "mouseover",  // 이벤트 타입
    true,         // 버블링 여부
    true,         // 취소 가능 여부
    window,       // 이벤트가 발생한 윈도우
    0,            // 클릭 횟수
    0, 0,         // 화면 좌표
    0, 0,         // 클라이언트 좌표
    false         // 컨트롤 키 누름 여부
);

// 이벤트 발생시키기
document.getElementById("myDiv").dispatchEvent(event);

DOM 4 레벨 방식

MouseEvent() 생성자를 이용하여 MouseEvent 객체를 초기화 & 생성한 후, dispatchEvent 메서드로 이벤트를 발생시킨다. MouseEvent 이외에도 KeyboardEvent, FocusEvent 등 Event Interface를 상속받는 다양한 생성자가 있다.

Event Interface?
DOM에서 발생하는 이벤트를 나타낸다.
다양한 이벤트 종류는 아래 사이트에서 참고 가능하다.
Event Interface

// MouseEvent 생성자를 직접 사용
const event = new MouseEvent('mouseover', {
    bubbles: true,
    cancelable: true,
    view: window,
    // 다른 옵션들도 한번에 설정 가능
});
// 이벤트 발생시키기
document.getElementById("myDiv").dispatchEvent(event);

다음은 활용 예시 코드이다. 이런식으로 이벤트 시뮬레이션을 사용하면 자동화된 테스트를 작성할 수 있고, 사용자 상호작용이 필요한 복잡한 기능도 쉽게 테스트할 수 있다.

function testButtonClick() {
    // 버튼 클릭 이벤트 시뮬레이션
    const clickEvent = new MouseEvent('click', {
        bubbles: true,
        cancelable: true
    });
    
    const button = document.getElementById('submitButton');
    button.dispatchEvent(clickEvent);
    
    // 이벤트 발생 후의 결과 확인하기
    assert(document.getElementById('result').textContent === '성공');
}

참고자료

Debounce 와 throttle 은 뭐고 각각 언제 사용할까?

JavaScript | Event 메모리 최적화와 Event 시뮬레이션

쓰로틀링과 디바운싱 알아보기

MDN - requestAnimationFrame()

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

0개의 댓글

관련 채용 정보