HTML5 앱의 성능 개선

소개

HTML5는 웹 애플리케이션의 시각적 모양을 향상할 수 있는 훌륭한 도구를 제공합니다. 애니메이션의 영역에서 특히 그렇습니다. 하지만 이 새로운 힘에는 새로운 과제도 따릅니다. 실제로 이러한 문제는 그리 새로운 문제가 아니며 친절한 책상 이웃인 플래시 프로그래머에게 과거에 비슷한 문제를 어떻게 극복했는지 물어보는 것이 좋을 수도 있습니다.

어쨌든, 애니메이션 작업을 할 때는 사용자가 이러한 애니메이션이 매끄럽다고 인식하는 것이 매우 중요합니다. 우리가 알아야 하는 것은, 인지 임곗값 이상으로 초당 프레임 수를 늘리는 것만으로는 애니메이션의 부드러움을 실제로 생성할 수 없다는 것입니다. 불행히도 우리의 뇌는 그것보다 더 똑똑합니다. 실제 초당 애니메이션 프레임 수 (fps)가 30fps인 것이 60fps보다 몇 개의 프레임만 중간에 드롭하는 것보다 훨씬 낫다는 것을 배우게 됩니다. 사람들은 들쭉날쭉한 것을 싫어합니다.

이 도움말에서는 애플리케이션의 환경을 개선하기 위한 도구와 기법을 제공합니다.

전략

HTML5를 사용하여 멋지고 놀라울 정도로 시각적인 앱을 개발하지 말라고 해서는 안 됩니다.

그런 다음 성능이 약간 개선될 수 있음을 알게 되면 여기로 돌아와서 애플리케이션의 요소를 개선할 수 있는 방법을 읽어보세요. 물론 처음부터 올바르게 일을 처리하는 데 도움이 될 수 있지만 그렇다고 해서 생산적인 작업에 지장을 주지는 마세요.

HTML5를 사용한 시각적 충실도++

하드웨어 가속

하드웨어 가속은 브라우저의 전반적인 렌더링 성능에 있어 중요한 단계입니다. 일반적인 구성표는 주 CPU에서 계산해야 하는 작업을 컴퓨터 그래픽 어댑터의 그래픽 처리 장치 (GPU)로 오프로드하는 것입니다. 이렇게 하면 성능이 크게 향상될 수 있으며 휴대기기에서의 리소스 소비도 줄일 수 있습니다.

GPU를 통해 문서의 이러한 부분들을 가속화할 수 있습니다.

  • 일반 레이아웃 합성
  • CSS3 전환
  • CSS3 3D 변환
  • 캔버스 그림
  • WebGL 3D 그리기

캔버스 및 WebGL의 가속은 특정 애플리케이션에 적용되지 않을 수 있는 특수한 목적의 기능이지만 처음 세 가지 측면은 거의 모든 앱의 속도를 높이는 데 도움이 될 수 있습니다.

어떤 항목을 가속화할 수 있나요?

GPU 가속은 잘 정의된 특정 작업을 특수 목적 하드웨어로 오프로드하는 방식으로 작동합니다. 일반적으로 속도가 빠른 페이지 요소에 영향을 주지 않는 여러 개의 '레이어'로 문서가 분할됩니다. 이러한 레이어는 기존 렌더링 파이프라인을 사용하여 렌더링됩니다. 그런 다음 GPU를 사용하여 레이어를 단일 페이지에 합성하고 즉석에서 가속할 수 있는 '효과'를 적용합니다. 화면에서 애니메이션으로 표시되는 객체는 애니메이션이 실행되는 동안 페이지의 단일 '재레이아웃'을 요구하지 않을 수 있습니다.

이 점을 감안해야 할 점은 렌더링 엔진이 GPU 가속 매직을 적용할 수 있는 시점을 쉽게 식별할 수 있도록 해야 한다는 것입니다. 다음 예를 참고하세요.

이 방법이 작동하기는 하지만, 브라우저는 사용자가 매끄러운 애니메이션으로 인식해야 하는 작업을 수행하고 있는지 실제로 알지 못합니다. 대신 CSS3 전환을 사용하여 동일한 시각적 모양을 얻을 때 어떤 일이 일어나는지 생각해 보세요.

브라우저에서 이 애니메이션을 구현하는 방법은 개발자에게 완전히 숨겨집니다. 따라서 브라우저에서 GPU 가속과 같은 트릭을 적용하여 정의된 목표를 달성할 수 있습니다.

Chrome에는 GPU 가속 디버깅에 도움이 되는 유용한 명령줄 플래그가 두 가지 있습니다.

  1. --show-composited-layer-borders는 GPU 수준에서 조작되는 요소 주위에 빨간색 테두리를 표시합니다. GPU 레이어 내에서 조작이 이루어지는지 확인하는 데 유용합니다.
  2. --show-paint-rects: GPU가 아닌 모든 변경사항이 페인팅되며 다시 페인트된 모든 영역 주위에 밝은 테두리가 표시됩니다. 브라우저가 작동하는 페인트 영역을 최적화하는 것을 확인할 수 있습니다.

Safari에는 여기에 설명된 유사한 런타임 플래그가 있습니다.

CSS3 전환

CSS 전환은 모든 사용자에게 스타일 애니메이션을 간단하게 만들어 줄 뿐만 아니라 스마트한 성능 기능입니다. CSS 전환은 브라우저에 의해 관리되기 때문에 애니메이션의 충실도를 크게 개선할 수 있으며 대부분의 경우 하드웨어 가속이 사용됩니다. 현재 WebKit (Chrome, Safari, iOS)에는 하드웨어 가속 CSS 변환이 포함되어 있지만 다른 브라우저 및 플랫폼에서도 빠르게 사용할 수 있게 될 예정입니다.

transitionEnd 이벤트를 사용하여 이를 강력한 조합으로 스크립팅할 수 있습니다. 하지만 현재 지원되는 모든 전환 종료 이벤트를 캡처하려면 webkitTransitionEnd transitionend oTransitionEnd을 시청해야 합니다.

많은 라이브러리에는 전환이 있으면 이를 활용하고 그렇지 않으면 표준 DOM 스타일 애니메이션으로 대체하는 애니메이션 API가 도입되었습니다. scripty2, YUI 전환, jQuery 애니메이션 향상

CSS3 번역

이전에 페이지 전체에서 요소의 x/y 위치에 애니메이션을 적용하신 적이 있을 것입니다. 인라인 스타일의 왼쪽 및 상단 속성을 조작했을 수 있습니다. 2D 변환의 경우 translate() 기능을 사용하여 이 동작을 복제할 수 있습니다.

이것을 DOM 애니메이션과 결합하여 가능한 최상의 결과를 사용할 수 있습니다.

<div style="position:relative; height:120px;" class="hwaccel">

  <div style="padding:5px; width:100px; height:100px; background:papayaWhip;
              position:absolute;" id="box">
  </div>
</div>

<script>
document.querySelector('#box').addEventListener('click', moveIt, false);

function moveIt(evt) {
  var elem = evt.target;

  if (Modernizr.csstransforms && Modernizr.csstransitions) {
    // vendor prefixes omitted here for brevity
    elem.style.transition = 'all 3s ease-out';
    elem.style.transform = 'translateX(600px)';

  } else {
    // if an older browser, fall back to jQuery animate
    jQuery(elem).animate({ 'left': '600px'}, 3000);
  }
}
</script>

Modernizr를 사용하여 CSS 2D 변환과 CSS 전환에 대한 기능 테스트를 수행합니다. 그렇다면 변환을 사용하여 위치를 이동하겠습니다. 전환을 사용하여 애니메이션을 적용하면 브라우저가 하드웨어에서 전환을 가속화할 수 있습니다. 브라우저가 올바른 방향으로 다시 푸시되도록 하기 위해 위에서 '매직 CSS 글머리 기호'를 사용하겠습니다.

브라우저의 성능이 부족한 경우 jQuery로 대체하여 요소를 이동합니다. Louis-Remi Babe의 jQuery Transform polyfill 플러그인을 선택하여 이 모든 작업을 자동으로 수행할 수 있습니다.

window.requestAnimationFrame

requestAnimationFrame는 Mozilla에서 도입한 것으로, DOM/CSS 기반이든 <canvas> 또는 WebGL이든 상관없이 애니메이션을 실행하기 위한 네이티브 API를 제공하기 위해 WebKit에서 반복했습니다. 브라우저는 동시 실행 애니메이션을 단일 리플로우 및 리페인트 주기로 함께 최적화할 수 있으므로 애니메이션의 충실도가 높아집니다. 예를 들어 CSS 전환 또는 SVG SMIL과 동기화된 JS 기반 애니메이션입니다. 또한 표시되지 않는 탭에서 애니메이션 루프를 실행하면 브라우저에서 계속 실행하지 않습니다. 즉, CPU, GPU, 메모리 사용량이 감소하여 배터리 수명이 훨씬 길어집니다.

requestAnimationFrame를 사용하는 방법과 이유에 관한 자세한 내용은 폴 아일랜드어의 스마트 애니메이션용 requestAnimationFrame 자료를 참고하세요.

프로파일링

애플리케이션 속도를 개선할 수 있다는 사실을 알게 되면 프로파일링을 파헤쳐 최적화가 가장 큰 이점을 얻을 수 있는 부분을 알아내야 할 때입니다. 최적화는 소스 코드의 유지관리성에 부정적인 영향을 미치는 경우가 많으므로 필요한 경우에만 적용해야 합니다. 프로파일링을 통해 성능을 개선할 때 코드의 어떤 부분이 가장 큰 이점을 얻을 수 있는지 알 수 있습니다.

JavaScript 프로파일링

JavaScript 프로파일러는 각 개별 함수를 처음부터 끝까지 실행하는 데 걸리는 시간을 측정하여 JavaScript 함수 수준에서 애플리케이션의 성능을 간략하게 보여줍니다.

함수의 총 실행 시간은 하향식으로 함수를 실행하는 데 걸리는 전체 시간입니다. 순 실행 시간은 총 실행 시간에서 함수에서 호출된 함수를 실행하는 데 걸린 시간을 뺀 값입니다.

일부 함수는 다른 함수보다 더 자주 호출됩니다. 프로파일러는 일반적으로 모든 호출을 실행하는 데 걸린 시간과 평균, 최소, 최대 실행 시간을 제공합니다.

자세한 내용은 프로파일링에 관한 Chrome 개발자 도구 문서를 참고하세요.

DOM

JavaScript의 성능은 애플리케이션의 유동성과 반응성에 큰 영향을 미칩니다. JavaScript 프로파일러는 JavaScript의 실행 시간을 측정하는 반면, DOM 작업을 수행하는 데 소요된 시간도 간접적으로 측정한다는 점을 이해해야 합니다. 이러한 DOM 작업은 성능 문제의 중심에 있는 경우가 많습니다.

function drawArray(array) {
  for(var i = 0; i < array.length; i++) {
    document.getElementById('test').innerHTML += array[i]; // No good :(
  }
}

예를 들어 위의 코드에서는 실제 JavaScript를 실행하는 데 소요되는 시간이 거의 없습니다. drawArray-함수가 DOM과 상호작용하기 때문에 매우 낭비적인 방식으로 프로필에 표시될 가능성이 매우 높습니다.

도움말 및 유용한 정보

익명 함수

익명 함수는 기본적으로 프로파일러에 표시될 수 있는 이름이 없기 때문에 프로파일링하기가 쉽지 않습니다. 이 문제를 해결하는 방법에는 두 가지가 있습니다.

$('.stuff').each(function() { ... });

다음으로 재작성:

$('.stuff').each(function workOnStuff() { ... });

JavaScript가 함수 표현식 이름 지정을 지원한다는 것은 일반적으로 알려져 있지 않습니다. 이렇게 하면 프로파일러에 완벽하게 표시됩니다. 이 솔루션에는 한 가지 문제가 있습니다. 이름이 지정된 표현식은 실제로 함수 이름을 현재 어휘 범위에 넣습니다. 이렇게 하면 다른 기호에 문제가 발생할 수 있으므로 주의하시기 바랍니다.

긴 함수 프로파일링

긴 함수가 있고 그 일부가 성능 문제의 원인일 수 있다고 생각한다고 가정해 보겠습니다. 어떤 부분이 문제가 되는지 확인하는 방법에는 두 가지가 있습니다.

  1. 올바른 방법: 긴 함수를 포함하지 않도록 코드를 리팩터링합니다.
  2. 사악한 작업 완료 메서드: 이름이 지정된 자체 호출 함수 형태로 문을 코드에 추가합니다. 이로 인해 의미 체계가 변경되지 않으며, 이로 인해 함수의 일부가 프로파일러에서 개별 함수로 표시됩니다. js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... } 프로파일링이 완료된 후에는 이러한 추가 함수를 삭제하는 것을 잊지 마세요. 또는 시작점으로 사용하여 코드 리팩터링을 시작할 수도 있습니다.

DOM 프로파일링

최신 Chrome Web Inspector 개발 도구에는 브라우저에서 실행하는 하위 수준의 작업의 타임라인을 보여주는 새로운 '타임라인 뷰'가 포함되어 있습니다. 이 정보를 사용하여 DOM 작업을 최적화할 수 있습니다. 코드가 실행되는 동안 브라우저가 수행해야 하는 '작업'의 수를 줄이는 것을 목표로 해야 합니다.

타임라인 뷰는 엄청난 양의 정보를 만들 수 있습니다. 따라서 독립적으로 실행할 수 있는 최소한의 테스트 사례를 만들어야 합니다.

DOM 프로파일링

위의 이미지는 매우 간단한 스크립트의 타임라인 뷰 출력을 보여줍니다. 왼쪽 창에는 브라우저에서 수행한 작업이 시간순으로 표시되고 오른쪽 창의 타임라인에는 개별 작업에 소비된 실제 시간이 표시됩니다.

타임라인 뷰에 대한 추가 정보 Internet Explorer에서 프로파일링을 위한 대체 도구는 DynaTrace Ajax Edition입니다.

프로파일링 전략

측면 단일화

애플리케이션을 프로파일링할 때는 속도 저하를 유발할 수 있는 기능의 측면을 최대한 철저히 찾아 보세요. 그런 다음 애플리케이션의 이러한 측면과 관련된 코드 부분만 실행하는 프로필 실행을 시도해 봅니다. 이렇게 하면 프로파일링 데이터가 실제 문제와 관련이 없는 코드 경로와 혼합되지 않기 때문에 더 쉽게 해석할 수 있습니다. 애플리케이션의 개별 측면에 대한 좋은 예는 다음과 같습니다.

  1. 시작 시간 (프로파일러 활성화, 애플리케이션 새로고침, 초기화가 완료될 때까지 대기, 프로파일러 중지
  2. 버튼을 클릭한 후 후속 애니메이션 (프로파일러 시작, 버튼 클릭, 애니메이션이 완료될 때까지 대기, 프로파일러 중지)을 클릭합니다.
GUI 프로파일링

GUI 프로그램에서 3D 엔진의 레이 추적 프로그램을 최적화할 때보다 애플리케이션의 올바른 부분만 실행하는 것이 더 어려울 수 있습니다. 예를 들어 버튼을 클릭할 때 발생하는 작업을 프로파일링하려는 경우 관련 없는 마우스 오버 이벤트를 트리거하여 결과의 확정성이 떨어질 수 있습니다. 이러한 상황이 발생하지 않도록 하세요 :)

프로그래밍 인터페이스

디버거를 활성화하는 프로그래매틱 인터페이스도 있습니다. 따라서 프로파일링이 시작되는 시점과 종료 시점을 정밀하게 제어할 수 있습니다.

다음을 사용하여 프로파일링을 시작합니다.

console.profile()

다음을 사용하여 프로파일링을 중지합니다.

console.profileEnd()

반복 가능성

프로파일링을 수행할 때는 결과를 실제로 재현할 수 있는지 확인하세요. 이렇게 해야 최적화가 실제로 개선되었는지 여부를 알 수 있습니다. 또한 함수 수준 프로파일링은 전체 컴퓨터의 컨텍스트에서 수행됩니다. 정확한 과학은 아닙니다. 개별 프로필 실행은 컴퓨터에서 발생하는 다음과 같은 많은 영향을 받을 수 있습니다.

  1. 다른 항목을 측정하는 동안 실행되는 자체 애플리케이션에서 관련 없는 타이머
  2. 작업을 수행하는 가비지 컬렉터
  3. 브라우저의 다른 탭이 동일한 운영 스레드에서 열심히 작업하고 있습니다.
  4. 컴퓨터의 다른 프로그램이 CPU를 사용하여 응용 프로그램의 속도를 저하시킵니다.
  5. 지구의 중력장에 급격한 변화가 일어납니다.

하나의 프로파일링 세션에서 동일한 코드 경로를 여러 번 실행하는 것도 타당합니다. 이렇게 하면 위 요인의 영향을 줄이고 느린 부분이 더 선명하게 보일 수 있습니다.

측정, 개선, 측정

프로그램에서 느린 지점을 발견하면 실행 동작을 개선할 방법을 생각해 보세요. 코드를 변경한 후 프로필을 다시 설정합니다. 결과가 만족스러우면 계속 진행합니다. 개선되지 않으면 변경사항을 롤백하고 '상처를 주지 않으므로'에 그대로 두지 않아야 합니다.

최적화 전략

DOM 상호작용 최소화

웹 클라이언트 애플리케이션 속도를 향상시키기 위한 일반적인 주제는 DOM 상호작용을 최소화하는 것입니다. JavaScript 엔진의 속도는 엄청나게 증가했지만, DOM 액세스 속도는 그만큼 빨라지지 않았습니다. 이는 결코 발생하지 않을 매우 실용적인 이유이기도 합니다. 화면에 레이아웃 및 그리기와 같은 작업은 시간이 걸립니다.

DOM 노드 캐시

DOM에서 노드 또는 노드 목록을 검색할 때마다 이후 계산 (또는 다음 루프 반복)에서 이를 재사용할 수 있는지 생각해 보세요. 관련 영역에서 실제로 노드를 추가하거나 삭제하지 않는 한 이러한 경우가 많습니다.

변경 전:

function getElements() {
  return $('.my-class');
}

변경 후:

var cachedElements;
function getElements() {
  if (cachedElements) {
    return cachedElements;
  }
  cachedElements = $('.my-class');
  return cachedElements;
}

캐시 속성 값

DOM 노드를 캐시하는 것과 같은 방식으로 속성 값을 캐시할 수도 있습니다. 노드 스타일의 속성에 애니메이션을 적용한다고 가정해 보겠습니다. (코드의 해당 부분에서처럼) 자신이 이 속성에 영향을 미치는 유일한 사람이라는 것을 알고 있는 경우, 반복을 읽지 않아도 되도록 반복할 때마다 마지막 값을 캐시할 수 있습니다.

변경 전:

setInterval(function() {
  var ele = $('#element');
  var left = parseInt(ele.css('left'), 10);
  ele.css('left', (left + 5) + 'px');
}, 1000 / 30);

이후: js var ele = $('#element'); var left = parseInt(ele.css('left'), 10); setInterval(function() { left += 5; ele.css('left', left + 'px'); }, 1000 / 30);

DOM 조작을 루프 외부로 이동

루프는 종종 최적화의 핫 포인트입니다. DOM 사용에 대한 실제 숫자 처리와 분리하는 방법을 생각해 보세요. 보통 계산한 다음 계산이 완료되면 모든 결과를 한 번에 적용할 수 있습니다.

변경 전:

document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  document.getElementById('target').innerHTML += val;
}

변경 후:

var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
  var val = doSomething(array[i]);
  stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');

다시 그리기 및 리플로우

앞서 설명한 것처럼 DOM 액세스는 비교적 느립니다. 코드가 최근에 DOM에서 관련된 무언가를 수정했기 때문에 다시 계산해야 하는 값을 코드가 읽을 때 속도가 매우 느려집니다. 따라서 DOM에 대한 읽기 및 쓰기 액세스를 혼합하지 않아야 합니다. 코드를 항상 두 단계로 그룹화하는 것이 이상적입니다.

  • 1단계: 코드에 필요한 DOM 값 읽기
  • 2단계: DOM 수정

다음과 같은 패턴을 프로그래밍하지 마세요.

  • 1단계: DOM 값 읽기
  • 2단계: DOM 수정
  • 3단계: 자세히 알아보기
  • 4단계: 다른 위치에서 DOM 수정하기

변경 전:

function paintSlow() {
  var left1 = $('#thing1').css('left');
  $('#otherThing1').css('left', left);
  var left2 = $('#thing2').css('left');
  $('#otherThing2').css('left', left);
}

변경 후:

function paintFast() {
  var left1 = $('#thing1').css('left');
  var left2 = $('#thing2').css('left');
  $('#otherThing1').css('left', left);
  $('#otherThing2').css('left', left);
}

이 조언은 하나의 JavaScript 실행 컨텍스트 내에서 발생하는 작업에 고려해야 합니다. (예: 이벤트 핸들러 내에서, 간격 핸들러 내에서 또는 Ajax 응답을 처리할 때).

위의 paintSlow() 함수를 실행하면 다음 이미지가 생성됩니다.

paintSlow()

더 빠른 구현으로 전환하면 다음 이미지가 생성됩니다.

빠른 구현

이 이미지는 코드가 DOM에 액세스하는 방식을 재정렬하면 렌더링 성능이 크게 향상될 수 있음을 보여줍니다. 이 경우 동일한 결과를 만들기 위해 원래 코드가 스타일을 다시 계산하고 페이지를 두 번 배치해야 합니다. 유사한 최적화를 기본적으로 모든 '실제' 코드에 적용할 수 있으며 매우 극적인 결과를 얻을 수 있습니다.

자세히 알아보기: 렌더링: 다시 페인트, 리플로우/재배치, 리스타일 스토얀 스테파노프

다시 그리기 및 이벤트 루프

브라우저에서 JavaScript를 실행하면 '이벤트 루프' 모델을 따릅니다. 기본적으로 브라우저는 '유휴' 상태입니다. 이 상태는 사용자 상호작용의 이벤트나 JavaScript 타이머 또는 Ajax 콜백 등에 의해 중단될 수 있습니다. 이러한 중단 지점에서 JavaScript가 실행될 때마다 브라우저는 일반적으로 화면을 다시 그릴 때까지 완료될 때까지 기다립니다. 매우 오래 실행되는 JavaScript의 경우 또는 JavaScript 실행을 효과적으로 중단시키는 알림 상자와 같은 경우에는 예외가 있을 수 있습니다.

결과

  1. 자바스크립트 애니메이션 주기를 실행하는 데 1/30초 이상 걸리는 경우 애니메이션이 JS 실행 중에 다시 페인트되지 않으므로 매끄러운 애니메이션을 만들 수 없습니다. 사용자 이벤트도 처리할 것으로 예상되는 경우 훨씬 더 빨라야 합니다.
  2. 일부 JavaScript 작업을 조금 더 늦출 때까지 지연하는 것이 편리한 때도 있습니다. 예: setTimeout(function() { ... }, 0) 이는 이벤트 루프가 다시 유휴 상태가 되는 즉시 콜백을 실행하도록 브라우저에 효과적으로 알립니다 (일부 브라우저는 사실상 10ms 이상 대기함). 이 경우 시간상으로 매우 가까운 두 개의 JavaScript 실행 주기가 생성된다는 점에 유의해야 합니다. 두 경우 모두 화면 다시 페인트를 트리거하여 전체 페인팅에 소요되는 시간을 두 배로 늘릴 수 있습니다. 이것이 실제로 두 페인트를 트리거하는지 여부는 브라우저의 휴리스틱에 따라 다릅니다.

일반 버전:

function paintFast() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  $('#otherThing2').css('height', '20px');
}
다시 그리기 및 이벤트 루프

지연 시간을 추가해 보겠습니다.

function paintALittleLater() {
  var height1 = $('#thing1').css('height');
  var height2 = $('#thing2').css('height');
  $('#otherThing1').css('height', '20px');
  setTimeout(function() {
    $('#otherThing2').css('height', '20px');
  }, 10)
}
Delay(지연)

지연된 버전에서는 페이지에 대한 변경사항의 1/100에 불과하지만 브라우저에서 페인트를 두 번 하는 것으로 나타납니다.

지연 초기화

사용자는 빠르게 로드되고 반응성이 뛰어난 웹 앱을 원합니다. 그러나 사용자는 자신이 실행하는 동작에 따라 느리다고 인식하는 기준점이 다릅니다. 예를 들어 앱은 사용자가 마우스를 계속 움직이는 동안 사용자 경험이 저하될 수 있으므로 마우스 오버 이벤트에 대해 많은 계산을 실행해서는 안 됩니다. 그러나 사용자는 버튼을 클릭한 후 약간의 지연을 수락하는 데 익숙합니다.

따라서 초기화 코드가 최대한 늦게 실행되도록 하는 것이 합리적일 수 있습니다 (예: 사용자가 애플리케이션의 특정 구성요소를 활성화하는 버튼을 클릭할 때).

이전: js var things = $('.ele > .other * div.className'); $('#button').click(function() { things.show() });

이후: js $('#button').click(function() { $('.ele > .other * div.className').show() });

이벤트 위임

이벤트 핸들러를 페이지 전체에 분산하는 작업은 비교적 오랜 시간이 걸릴 수 있으며 요소가 동적으로 교체되어 이벤트 핸들러를 새 요소에 다시 연결해야 하므로 번거로울 수도 있습니다.

이 경우 이벤트 위임이라는 기법을 사용하면 해결됩니다. 개별 이벤트 핸들러를 요소에 연결하는 대신 이벤트 핸들러를 실제로 상위 노드에 연결하고 이벤트의 타겟 노드를 확인하여 이벤트에 관심이 있는지 확인하는 방식으로 많은 브라우저 이벤트의 버블링 특성을 사용합니다.

jQuery에서는 다음과 같이 쉽게 표현할 수 있습니다.

$('#parentNode').delegate('.button', 'click', function() { ... });

이벤트 위임을 사용하면 안 되는 경우

이벤트 위임을 사용하고 있는데 성능 문제가 발생한 경우와 반대의 경우도 있습니다. 기본적으로 이벤트 위임은 복잡도의 상수 초기화 시간을 허용합니다. 그러나 이벤트에 관심이 있는지 확인하는 비용은 이벤트를 호출할 때마다 지불해야 합니다. 특히 'mouseover' 또는 'mousemove'과 같이 자주 발생하는 이벤트의 경우 비용이 많이 들 수 있습니다.

일반적인 문제 및 해결책

$(document).ready에서 내가 하는 일에 시간이 오래 걸림

몰테의 개인적인 조언: $(document).ready에서는 절대로 아무것도 하지 마세요. 최종 양식으로 문서를 전송해 보세요. 이벤트 리스너를 등록할 수는 있지만 id-selector를 사용하거나 이벤트 위임을 사용하는 것만 가능합니다. 'mousemove' 등 비용이 많이 드는 이벤트의 경우 필요할 때까지 등록을 지연합니다 (관련 요소의 마우스 오버 이벤트).

실제 데이터를 가져오기 위해 Ajax 요청을 하는 등 정말로 필요한 작업이 있는 경우 멋진 애니메이션을 표시합니다. 애니메이션 GIF와 같은 경우 애니메이션을 데이터 URI로 포함하는 것이 좋습니다.

플래시 동영상을 페이지에 추가했기 때문에 모든 것이 매우 느립니다.

페이지에 Flash를 추가하면 창의 최종 레이아웃을 브라우저와 Flash 플러그인 간에 '협상'해야 하므로 항상 렌더링 속도가 약간 느려집니다. 페이지에 Flash를 반드시 배치해야 하는 경우 'wmode' 플래시 매개변수를 'window' (기본값) 값으로 설정해야 합니다. 이렇게 하면 HTML과 플래시 요소를 합성하는 기능이 사용 중지됩니다. 플래시 동영상 위에 있는 HTML 요소를 볼 수 없으며 플래시 동영상은 투명하지 않습니다. 이 경우 불편할 수 있지만 실적을 크게 개선할 수 있습니다. 예를 들어 youtube.com에서 기본 영화 플레이어 위에 레이어를 배치하지 않도록 주의 깊게 살펴보세요.

로컬 저장소에 저장하려고 하는데 애플리케이션이 멈춥니다.

localStorage에 쓰는 것은 하드 디스크를 가동하는 동기식 작업입니다. 애니메이션을 실행하는 동안에는 '장기 실행' 동기 작업을 실행하지 않는 것이 좋습니다. 코드에서 사용자가 유휴 상태이고 애니메이션이 진행되지 않는 것이 확실한 위치로 localStorage 액세스 권한을 이동합니다.

프로파일링은 jQuery 선택기가 매우 느리다는 것을 가리킵니다.

먼저 document.querySelectorAll을 통해 선택기를 실행할 수 있는지 확인해야 합니다. JavaScript 콘솔에서 이를 테스트할 수 있습니다. 예외가 발생하면 JavaScript 프레임워크의 특수 확장을 사용하지 않도록 선택기를 다시 작성합니다. 이렇게 하면 최신 브라우저에서 선택기 속도가 훨씬 빨라집니다.

이 방법이 도움이 되지 않거나 최신 브라우저에서도 속도를 빠르게 하려면 다음 가이드라인을 따르세요.

  • 가능한 한 선택 도구의 오른쪽을 구체적으로 작성합니다.
  • 가장 오른쪽에 있는 선택기 부분으로 자주 사용하지 않는 태그 이름을 사용합니다.
  • 도움이 되지 않는다면 ID 선택기를 사용할 수 있도록 다시 작성해 보세요.

이러한 모든 DOM 조작에는 시간이 오래 걸립니다.

다수의 DOM 노드 삽입, 삭제 및 업데이트는 매우 느릴 수 있습니다. 이는 일반적으로 큰 html 문자열을 생성하고 domNode.innerHTML = newHTML를 사용하여 이전 콘텐츠를 대체함으로써 최적화할 수 있습니다. 이는 유지관리에 매우 좋지 않을 수 있으며 IE에서 메모리 링크를 만들 수 있으므로 주의해야 합니다.

또 다른 일반적인 문제는 초기화 코드로 많은 수의 HTML을 만들 수 있다는 것입니다. 예를 들어 jQuery 플러그인은 선택 상자를 여러 개의 div로 변환합니다. 이는 UX 모범 사례에는 무관하게 사람들이 원했던 디자인이기 때문입니다. 페이지 로드 속도를 높이고 싶다면 절대 그렇게 하지 마세요. 대신 서버 측의 모든 마크업을 최종 형태로 전달합니다. 여기에도 많은 문제가 있으므로 속도가 절충할 가치가 있는지 신중하게 생각하세요.

도구

  1. JSPerf - 자바스크립트의 작은 스니펫 벤치마킹하기
  2. Firebug - Firefox에서의 프로파일링용
  3. Chrome 개발자 도구 (Safari에서 WebInspector로 사용 가능)
  4. DOM Monster - DOM 성능 최적화
  5. DynaTrace Ajax 에디션 - Internet Explorer에서의 프로파일링 및 페인트 최적화용

추가 자료

  1. Google 속도
  2. jQuery 성능 관련 폴 아이리시
  3. 극단적인 자바스크립트 성능 (슬라이드 자료)