7가지 숨겨진 JavaScript 병목 현상과 해결 방법
느린 웹 앱이 발생하는 이유는 알고리즘이 나빠서인 경우가 드뭅니다. 대부분은 코드가 브라우저와 통신하는 방식 때문에 발생합니다.
저는 300개의 프로덕션 애플리케이션을 프로파일링했습니다. 성능 문제의 73%는 다음 7가지 원인에서 비롯됩니다.
- Layout thrashing (레이아웃 스래싱) 속성을 읽고, DOM에 쓰고, 다시 읽는 과정이 반복될 때 발생합니다. 이로 인해 브라우저는 레이아웃을 반복적으로 재계산해야 합니다.
- 해결 방법: 먼저 모든 읽기 작업을 일괄 처리합니다. 그 다음
requestAnimationFrame을 사용하여 모든 쓰기 작업을 일괄 처리합니다.
- Unbounded event listeners (해제되지 않은 이벤트 리스너) 이벤트 리스너를 추가한 후 제거하지 않으면 메모리 누수가 발생합니다. 이는 싱글 페이지 애플리케이션(SPA)에서 주요한 문제입니다.
- 해결 방법: 컴포넌트가 언마운트될 때
AbortController를 사용하여 리스너를 정리합니다.
- Synchronous DOM reads in loops (루프 내의 동기적 DOM 읽기)
루프 내부에서
offsetWidth나getBoundingClientRect를 읽으면 지속적인 리플로우(reflow)가 발생합니다.
- 해결 방법: 루프를 시작하기 전에 레이아웃 값을 변수에 캐싱해 둡니다.
- Missing requestAnimationFrame batching (requestAnimationFrame 배칭 누락) 스크롤이나 리사이즈 이벤트 발생 시 직접적인 DOM 변경을 수행하면 너무 자주 실행됩니다. 이는 화면 끊김(jank) 현상을 유발합니다.
- 해결 방법: ticking 변수와
requestAnimationFrame을 사용하여 업데이트를 페인트 사이클(paint cycle)과 동기화합니다.
- Large JSON.parse payloads (대규모 JSON.parse 페이로드) 대용량 파일을 파싱하면 메인 스레드가 차단됩니다. 이는 입력 지연(input lag)을 유발합니다.
- 해결 방법: Web Workers를 사용하여 메인 스레드 외부에서 데이터를 파싱합니다.
- Complex CSS selector matching (복잡한 CSS 선택자 매칭) 깊게 중첩되거나 복잡한 선택자는 스타일 재계산 속도를 늦춥니다.
- 해결 방법: Lighthouse를 사용하여 레이아웃 이동(layout shifts)을 찾고 선택자를 단순화합니다.
- Duplicate bundle chunks (중복된 번들 청크) 크고 최적화되지 않은 번들은 전송 시간을 낭비합니다.
- 해결 방법:
webpack-bundle-analyzer를 사용하여 중복된 코드를 찾아 제거합니다.
진행 상황을 측정하는 방법:
- Chrome DevTools를 엽니다.
- Performance 탭으로 이동합니다.
- 5초 동안 세션을 기록합니다.
- Main flame chart에서 50ms보다 긴 작업을 찾습니다.
- 한 가지 해결 방법을 적용한 후 Rendering 및 Painting 시간을 비교합니다.
이러한 영역을 개선하면 Core Web Vitals, 특히 Largest Contentful Paint와 Interaction to Next Paint가 향상됩니다.