자바스크립트 실행 최적화

JavaScript는 시각적 변경사항을 트리거하는 경우가 많습니다. 스타일 조작을 통해 직접 변경되는 경우도 있고, 데이터 검색이나 정렬과 같이 시각적 변경사항을 초래하는 계산을 통해 변경되는 경우도 있습니다. 타이밍이 잘못되거나 오래 실행되는 JavaScript는 성능 문제의 일반적인 원인입니다. 가능한 한 영향을 최소화해야 합니다.

JavaScript는 시각적 변경사항을 트리거하는 경우가 많습니다. 스타일 조작을 통해 직접 변경되는 경우도 있고, 데이터 검색이나 정렬과 같이 시각적 변경사항을 초래하는 계산을 통해 변경되는 경우도 있습니다. 타이밍이 잘못되었거나 장시간 실행되는 JavaScript는 성능 문제의 일반적인 원인입니다. 가능한 한 영향을 최소화해야 합니다.

작성한 JavaScript가 실제로 실행되는 코드와는 전혀 다르기 때문에 JavaScript 성능 프로파일링은 기술이 필요할 수 있습니다. 최신 브라우저는 JIT 컴파일러와 모든 종류의 최적화 및 트릭을 사용하여 최대한 빠른 실행을 제공하려고 시도하며, 이로 인해 코드의 동역학이 크게 달라집니다.

하지만 앱이 JavaScript를 잘 실행하도록 돕기 위해 할 수 있는 몇 가지 작업이 있습니다.

요약

  • 시각적 업데이트에 setTimeout 또는 setInterval을 사용하지 마세요. 대신 항상 requestAnimationFrame을 사용하세요.
  • 장기 실행 JavaScript를 기본 스레드에서 웹 작업자로 이동합니다.
  • 마이크로 태스크를 사용하여 여러 프레임에 걸쳐 DOM을 변경합니다.
  • Chrome DevTools의 타임라인 및 JavaScript 프로파일러를 사용하여 JavaScript의 영향을 평가합니다.

시각적 변경사항에 requestAnimationFrame 사용

화면에서 시각적 변경사항이 발생할 때 브라우저에 적합한 시점, 즉 프레임 시작 시점에 작업을 실행해야 합니다. JavaScript가 프레임 시작 시 실행되도록 보장하는 유일한 방법은 requestAnimationFrame를 사용하는 것입니다.

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

프레임워크나 샘플은 setTimeout 또는 setInterval를 사용하여 애니메이션과 같은 시각적 변경사항을 실행할 수 있지만, 이 경우 콜백이 프레임의 어느 시점(아마도 마지막)에 실행되므로 프레임을 놓쳐 버벅거림이 발생할 수 있습니다.

setTimeout으로 인해 브라우저에서 프레임을 놓치는 문제

사실 jQuery는 animate 동작에 setTimeout를 사용했습니다. 버전 3에서 requestAnimationFrame를 사용하도록 변경되었습니다. 이전 버전의 jQuery를 사용하는 경우 requestAnimationFrame를 사용하도록 패치할 수 있으며, 이는 적극 권장됩니다.

복잡성 줄이기 또는 웹 작업자 사용

JavaScript는 스타일 계산, 레이아웃, 많은 경우 페인트와 함께 브라우저의 기본 스레드에서 실행됩니다. JavaScript가 장시간 실행되면 이러한 다른 작업이 차단되어 프레임이 누락될 수 있습니다.

JavaScript가 실행되는 시점과 실행 시간에 대해 전략적으로 접근해야 합니다. 예를 들어 스크롤과 같은 애니메이션이 실행 중인 경우 JavaScript를 3~4ms 범위로 유지하는 것이 좋습니다. 그 이상이면 시간이 너무 오래 걸릴 수 있습니다. 유휴 상태인 경우 소요되는 시간에 대해 더 느긋하게 생각할 수 있습니다.

예를 들어 DOM 액세스가 필요하지 않은 경우 순수 계산 작업을 웹 워커로 이동할 수 있습니다. 정렬이나 검색과 같은 데이터 조작 또는 탐색은 로드 및 모델 생성과 마찬가지로 이 모델에 적합한 경우가 많습니다.

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

모든 작업이 이 모델에 적합한 것은 아닙니다. 웹 워커는 DOM에 액세스할 수 없습니다. 작업이 기본 스레드에 있어야 하는 경우 더 큰 태스크를 각각 몇 밀리초가 넘지 않는 마이크로 태스크로 분할하고 각 프레임에서 requestAnimationFrame 핸들러 내에서 실행하는 일괄 처리 접근 방식을 고려해 보세요.

이 접근 방식에는 UX 및 UI 관련 결과가 있으며 진행률 또는 활동 표시기 사용을 통해 사용자가 작업이 처리되고 있음을 알 수 있도록 해야 합니다. 어쨌든 이 접근 방식을 사용하면 앱의 기본 스레드가 확보되어 사용자 상호작용에 계속 응답할 수 있습니다.

JavaScript의 '프레임 세금' 알아보기

프레임워크, 라이브러리 또는 자체 코드를 평가할 때는 프레임별로 JavaScript 코드를 실행하는 데 드는 비용을 평가하는 것이 중요합니다. 이는 전환이나 스크롤과 같이 성능이 중요한 애니메이션 작업을 할 때 특히 중요합니다.

Chrome DevTools의 성능 패널은 JavaScript 비용을 측정하는 가장 좋은 방법입니다. 일반적으로 다음과 같은 하위 레벨 레코드가 표시됩니다.

Chrome DevTools의 성능 녹화

기본 섹션에는 JavaScript 호출의 플레임 차트가 제공되므로 호출된 함수와 각 함수의 소요 시간을 정확하게 분석할 수 있습니다.

이 정보를 바탕으로 JavaScript가 애플리케이션에 미치는 성능 영향을 평가하고 함수를 실행하는 데 시간이 너무 오래 걸리는 핫스팟을 찾아 수정할 수 있습니다. 앞서 언급한 대로 장기 실행 JavaScript를 삭제하거나, 불가능하다면 기본 스레드를 해제하여 다른 작업을 계속할 수 있도록 웹 워커로 이동해야 합니다.

성능 패널을 사용하는 방법은 런타임 성능 분석 시작하기를 참고하세요.

JavaScript 미세 최적화 피하기

브라우저가 어떤 작업의 한 버전을 다른 작업보다 100배 더 빠르게 실행할 수 있다는 것을 알면 멋질 수 있습니다. 예를 들어 요소의 offsetTop를 요청하는 것이 getBoundingClientRect()를 계산하는 것보다 빠릅니다. 하지만 이러한 함수는 프레임당 소수의 횟수만 호출된다는 것이 거의 항상 사실이므로 JavaScript 성능의 이 측면에 집중하는 것은 일반적으로 시간 낭비입니다. 일반적으로 밀리초 단위로만 절약됩니다.

게임이나 계산 비용이 많이 드는 애플리케이션을 만드는 경우 이 가이드는 예외가 될 수 있습니다. 일반적으로 많은 계산을 단일 프레임에 맞추기 때문에 이 경우 모든 것이 도움이 됩니다.

간단히 말해 마이크로 최적화는 일반적으로 빌드하는 애플리케이션 유형에 매핑되지 않으므로 매우 조심해야 합니다.