소개
HTML5는 웹 애플리케이션의 시각적 외관을 개선하는 데 유용한 도구를 제공합니다. 이는 특히 애니메이션 분야에서 그렇습니다. 하지만 이러한 새로운 힘에는 새로운 도전과제도 따릅니다. 사실 이러한 문제는 그리 새롭지 않으며, 친절한 책상 이웃인 Flash 프로그래머에게 과거에 비슷한 문제를 어떻게 해결했는지 물어보는 것이 좋을 때도 있습니다.
어쨌든 애니메이션을 사용할 때는 사용자가 애니메이션을 부드럽게 인식하는 것이 매우 중요합니다. 단순히 초당 프레임 수를 인식적 임곗값을 초과하도록 늘리는 것만으로는 애니메이션의 부드러움을 실제로 만들 수 없습니다. 안타깝게도 인간의 두뇌는 그보다 더 똑똑합니다. 실제 초당 30프레임 (fps) 애니메이션이 중간에 몇 프레임이 누락된 60fps보다 훨씬 낫다는 것을 알게 될 것입니다. 사람들은 모서리가 울퉁불퉁한 것을 싫어합니다.
이 도움말에서는 자체 애플리케이션의 환경을 개선하는 데 사용할 수 있는 도구와 기술을 제공합니다.
전략
HTML5로 멋지고 시선을 사로잡는 시각적 앱을 빌드하지 말라는 의미는 아닙니다.
성능을 조금 더 개선할 수 있다고 생각되면 여기로 돌아와 애플리케이션의 요소를 개선하는 방법을 알아보세요. 물론 처음부터 일을 제대로 하는 데 도움이 될 수 있지만, 그것이 생산성을 저해하게 해서는 안 됩니다.
HTML5를 통한 시각적 충실도++
하드웨어 가속
하드웨어 가속은 브라우저의 전반적인 렌더링 성능을 개선하는 데 중요한 이정표입니다. 일반적인 방식은 기본 CPU에서 계산할 태스크를 컴퓨터의 그래픽 어댑터에 있는 그래픽 처리 장치 (GPU)로 오프로드하는 것입니다. 이렇게 하면 성능이 크게 향상되고 휴대기기의 리소스 사용량도 줄일 수 있습니다.
GPU는 문서의 이러한 측면을 가속화할 수 있습니다.
- 일반 레이아웃 합성
- CSS3 전환
- CSS3 3D 변환
- 캔버스 그리기
- WebGL 3D 그리기
캔버스 및 WebGL 가속은 특정 애플리케이션에 적용되지 않을 수 있는 특수 목적의 기능이지만, 앞의 세 가지 측면은 거의 모든 앱의 속도를 높이는 데 도움이 될 수 있습니다.
무엇을 가속할 수 있나요?
GPU 가속은 잘 정의된 특정 작업을 특수 목적 하드웨어로 오프로드하여 작동합니다. 일반적인 스키마는 문서가 가속되는 페이지 측면과 관계없는 여러 '레이어'로 분할된다는 것입니다. 이러한 레이어는 기존 렌더링 파이프라인을 사용하여 렌더링됩니다. 그런 다음 GPU를 사용하여 레이어를 단일 페이지에 합성하고 즉시 가속할 수 있는 '효과'를 적용합니다. 화면에서 애니메이션이 적용되는 객체에 애니메이션이 실행되는 동안 페이지를 한 번만 '재구성'하면 됩니다.
여기서 중요한 점은 렌더링 엔진이 GPU 가속 마법을 적용할 수 있는 시점을 쉽게 식별할 수 있도록 해야 한다는 것입니다. 다음 예를 참고하세요.
이렇게 하면 작동하지만 브라우저는 사람이 부드러운 애니메이션으로 인식할 수 있는 작업을 실행하고 있다는 사실을 알지 못합니다. 대신 CSS3 전환을 사용하여 동일한 시각적 모양을 얻으면 어떻게 되는지 생각해 보세요.
브라우저가 이 애니메이션을 구현하는 방법은 개발자에게 완전히 숨겨져 있습니다. 즉, 브라우저가 GPU 가속과 같은 트릭을 적용하여 정의된 목표를 달성할 수 있습니다.
Chrome에는 GPU 가속 디버깅에 도움이 되는 두 가지 유용한 명령줄 플래그가 있습니다.
--show-composited-layer-borders
는 GPU 수준에서 조작되는 요소 주위에 빨간색 테두리를 표시합니다. GPU 레이어 내에서 조작이 발생하는지 확인하는 데 적합합니다.--show-paint-rects
GPU가 아닌 모든 변경사항이 페인트되고 다시 페인트되는 모든 영역 주위에 밝은 테두리가 표시됩니다. 브라우저가 페인트 영역을 최적화하는 모습을 확인할 수 있습니다.
Safari에는 여기에 설명된 것과 유사한 런타임 플래그가 있습니다.
CSS3 전환
CSS 전환을 사용하면 누구나 스타일 애니메이션을 간단하게 만들 수 있지만 스마트한 성능 기능이기도 합니다. CSS 전환은 브라우저에서 관리하므로 애니메이션의 충실도가 크게 개선될 수 있으며, 많은 경우 하드웨어 가속이 적용됩니다. 현재 WebKit (Chrome, Safari, iOS)에는 하드웨어 가속 CSS 변환이 있지만 다른 브라우저와 플랫폼에도 곧 제공될 예정입니다.
transitionEnd
이벤트를 사용하여 이를 강력한 조합으로 스크립트할 수 있지만 현재 지원되는 모든 전환 종료 이벤트를 캡처하려면 webkitTransitionEnd transitionend oTransitionEnd
를 시청해야 합니다.
이제 많은 라이브러리에서 전환이 있는 경우 전환을 활용하고 그렇지 않은 경우 표준 DOM 스타일 애니메이션으로 대체하는 애니메이션 API를 도입했습니다. scripty2, YUI transition, jQuery animate enhanced.
CSS3 Translate
이전에 페이지에서 요소의 x/y 위치를 애니메이션 처리한 적이 있을 것입니다. 인라인 스타일의 left 및 top 속성을 조작했을 수 있습니다. 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 전환을 기능 테스트합니다. 테스트 결과가 양수이면 translate를 사용하여 위치를 이동합니다. 전환을 사용하여 애니메이션이 적용된 경우 브라우저에서 하드웨어 가속을 적용할 가능성이 높습니다. 브라우저를 올바른 방향으로 다시 밀어주기 위해 위의 '마법 CSS 글머리기호'를 사용합니다.
브라우저 기능이 부족하면 jQuery로 대체하여 요소를 이동합니다. Louis-Remi Babe의 jQuery 변환 폴리필 플러그인을 사용하여 이 모든 작업을 자동화할 수 있습니다.
window.requestAnimationFrame
requestAnimationFrame
는 DOM/CSS 기반이든 <canvas>
또는 WebGL 기반이든 애니메이션을 실행하기 위한 네이티브 API를 제공하기 위해 Mozilla에서 도입되었으며 WebKit에서 반복되었습니다. 브라우저는 동시 애니메이션을 단일 리플로 및 다시 그리기 주기로 함께 최적화하여 더 높은 충실도 애니메이션을 제공할 수 있습니다. 예를 들어 CSS 전환 또는 SVG SMIL과 동기화된 JS 기반 애니메이션이 여기에 해당합니다. 또한 표시되지 않는 탭에서 애니메이션 루프를 실행하면 브라우저에서 계속 실행하지 않습니다. 즉, CPU, GPU, 메모리 사용량이 줄어 배터리 수명이 훨씬 길어집니다.
requestAnimationFrame
를 사용하는 방법과 이유에 관한 자세한 내용은 폴 아이리시의 도움말 스마트 애니메이션을 위한 requestAnimationFrame을 참고하세요.
프로파일링
애플리케이션 속도를 개선할 수 있다는 것을 알게 되면 프로파일링을 자세히 살펴보고 최적화가 가장 큰 이점을 얻을 수 있는 부분을 찾아야 합니다. 최적화는 소스 코드의 유지보수성에 부정적인 영향을 미치는 경우가 많으므로 필요한 경우에만 적용해야 합니다. 프로파일링을 통해 성능이 개선될 때 가장 큰 이점을 얻을 수 있는 코드 부분을 파악할 수 있습니다.
JavaScript 프로파일링
JavaScript 프로파일러는 각 개별 함수를 시작부터 끝까지 실행하는 데 걸리는 시간을 측정하여 JavaScript 함수 수준에서 애플리케이션의 성능을 개략적으로 보여줍니다.
함수의 총 실행 시간은 함수를 위에서 아래로 실행하는 데 걸리는 전체 시간입니다. 순 실행 시간은 총 실행 시간에서 함수에서 호출된 함수를 실행하는 데 걸린 시간을 뺀 값입니다.
일부 함수는 다른 함수보다 더 자주 호출됩니다. 프로파일러는 일반적으로 모든 호출이 실행되는 데 걸린 시간과 평균, 최소, 최대 실행 시간을 제공합니다.
자세한 내용은 프로파일링에 관한 Chrome DevTools 문서를 참고하세요.
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가 함수 표현식의 이름 지정을 지원한다는 사실은 잘 알려져 있지 않습니다. 이렇게 하면 프로파일러에 완벽하게 표시됩니다. 이 솔루션에는 한 가지 문제가 있습니다. 이름이 지정된 표현식은 실제로 함수 이름을 현재 렉시컬 범위에 배치합니다. 이렇게 하면 다른 기호가 클로버링될 수 있으므로 주의하세요.
긴 함수 프로파일링
긴 함수가 있고 그중 일부가 성능 문제의 원인일 수 있다고 생각해 보겠습니다. 문제가 있는 부분을 찾는 방법에는 두 가지가 있습니다.
- 올바른 방법: 긴 함수가 포함되지 않도록 코드를 리팩터링합니다.
- 나쁜 작업 완료 방법: 이름이 지정된 자체 호출 함수의 형태로 문을 코드에 추가합니다. 약간만 주의하면 시맨틱은 변경되지 않으며 함수의 일부가 프로파일러에 개별 함수로 표시됩니다.
js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... }
프로파일링이 완료된 후에는 이러한 추가 함수를 삭제해야 합니다. 또는 코드 리팩터링의 시작점으로 사용해도 됩니다.
DOM 프로파일링
최신 Chrome 웹 인스펙터 개발 도구에는 브라우저에서 실행되는 하위 수준 작업의 타임라인을 보여주는 새로운 '타임라인 보기'가 포함되어 있습니다. 이 정보를 사용하여 DOM 작업을 최적화할 수 있습니다. 코드가 실행되는 동안 브라우저에서 실행해야 하는 '작업'의 수를 줄이는 것이 좋습니다.
타임라인 보기는 엄청난 양의 정보를 생성할 수 있습니다. 따라서 독립적으로 실행할 수 있는 최소한의 테스트 사례를 만드는 것이 좋습니다.
위의 이미지는 매우 간단한 스크립트의 타임라인 뷰 출력을 보여줍니다. 왼쪽 창에는 브라우저에서 실행한 작업이 시간순으로 표시되고 오른쪽 창의 타임라인에는 개별 작업에 소요된 실제 시간이 표시됩니다.
타임라인 뷰에 대한 자세한 정보 Internet Explorer에서 프로파일링하는 대체 도구는 DynaTrace Ajax Edition입니다.
프로파일링 전략
특정 측면을 강조 표시
애플리케이션을 프로파일링하려면 느려질 수 있는 기능 측면을 최대한 가깝게 파악해 보세요. 그런 다음 애플리케이션의 이러한 측면과 관련된 코드 부분만 실행하는 프로필 실행을 시도합니다. 이렇게 하면 실제 문제와 관련 없는 코드 경로와 혼합되지 않으므로 프로파일링 데이터를 더 쉽게 해석할 수 있습니다. 애플리케이션의 개별 측면을 보여주는 좋은 예는 다음과 같습니다.
- 시작 시간 (프로파일러 활성화, 애플리케이션 새로고침, 초기화가 완료될 때까지 대기, 프로파일러 중지)
- 버튼과 후속 애니메이션을 클릭합니다 (프로파일러 시작, 버튼 클릭, 애니메이션이 완료될 때까지 기다림, 프로파일러 중지).
GUI 프로파일링
GUI 프로그램에서는 3D 엔진의 레이 트레이서를 최적화할 때보다 애플리케이션의 올바른 부분만 실행하는 것이 더 어려울 수 있습니다. 예를 들어 버튼을 클릭할 때 발생하는 항목을 프로파일링하려고 할 때는 그 과정에서 관련 없는 마우스오버 이벤트가 트리거되어 결과가 확실하지 않을 수 있습니다. 그러지 마세요. :)
프로그래매틱 인터페이스
디버거를 활성화하는 프로그래매틱 인터페이스도 있습니다. 이를 통해 프로파일링이 시작되고 종료되는 시점을 정확하게 제어할 수 있습니다.
다음과 같이 프로파일링을 시작합니다.
console.profile()
다음을 사용하여 프로파일링을 중지합니다.
console.profileEnd()
반복성
프로파일링을 할 때는 결과를 실제로 재현할 수 있는지 확인합니다. 그래야 최적화가 실제로 실적을 개선했는지 알 수 있습니다. 또한 함수 수준 프로파일링은 전체 컴퓨터의 컨텍스트에서 실행됩니다. 정확한 과학은 아닙니다. 개별 프로필 실행은 컴퓨터에서 발생하는 여러 요인에 영향을 받을 수 있습니다.
- 다른 항목을 측정하는 동안 실행되는 자체 애플리케이션의 관련 없는 타이머입니다.
- 가비지 컬렉터가 작동 중입니다.
- 브라우저의 다른 탭이 동일한 운영 스레드에서 열심히 작업하고 있습니다.
- 컴퓨터의 다른 프로그램이 CPU를 사용해 애플리케이션이 느려집니다.
- 지구의 중력장의 갑작스러운 변화
또한 하나의 프로파일링 세션에서 동일한 코드 경로를 여러 번 실행하는 것이 좋습니다. 이렇게 하면 위 요소의 영향이 줄어들고 느린 부분이 더 명확하게 눈에 띌 수 있습니다.
측정, 개선, 측정
프로그램에서 느린 부분을 확인했다면 실행 동작을 개선할 방법을 생각해 보세요. 코드를 변경한 후 다시 프로파일링합니다. 결과에 만족하면 계속 진행하고, 개선되지 않으면 변경사항을 롤백해야 합니다. '해가 될 수 없으니' 그대로 두지 마세요.
최적화 전략
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()
함수를 실행하면 다음 이미지가 생성됩니다.
더 빠른 구현으로 전환하면 다음 이미지가 표시됩니다.
이 이미지는 코드가 DOM에 액세스하는 방식을 재정렬하면 렌더링 성능을 크게 향상할 수 있음을 보여줍니다. 이 경우 동일한 결과를 만들기 위해 원래 코드가 스타일을 다시 계산하고 페이지 레이아웃을 두 번 실행해야 합니다. 이와 유사한 최적화를 기본적으로 모든 '실제' 코드에 적용할 수 있으며 상당히 극적인 결과를 얻을 수 있습니다.
자세히 알아보기: 스토얀 스테파노프의 렌더링: 다시 그리기, 리플로/레이아웃 변경, 스타일 변경
다시 그리기 및 이벤트 루프
브라우저에서 JavaScript 실행은 '이벤트 루프' 모델을 따릅니다. 기본적으로 브라우저는 '휴면' 상태입니다. 이 상태는 사용자 상호작용의 이벤트나 JavaScript 타이머, Ajax 콜백과 같은 요소에 의해 중단될 수 있습니다. 이러한 중단 지점에서 JavaScript가 실행될 때마다 브라우저는 일반적으로 화면을 다시 칠할 때까지 JavaScript가 완료될 때까지 기다립니다. (실행 시간이 매우 긴 JavaScript나 JavaScript 실행을 효과적으로 중단하는 알림 상자와 같은 경우에는 예외가 있을 수 있음).
결과
- JavaScript 애니메이션 주기가 실행되는 데 1/30초가 넘는 경우 브라우저가 JS 실행 중에 다시 칠하지 않으므로 부드러운 애니메이션을 만들 수 없습니다. 사용자 이벤트도 처리해야 하는 경우 훨씬 더 빠르게 처리해야 합니다.
- 일부 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)
}
지연된 버전에서는 페이지의 두 변경사항이 각각 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() { ... });
이벤트 위임을 사용하지 말아야 하는 경우
반대의 경우도 있습니다. 이벤트 위임을 사용하고 있는데 성능 문제가 발생하는 경우입니다. 기본적으로 이벤트 위임을 사용하면 일정한 복잡도의 초기화 시간을 허용할 수 있습니다. 그러나 이벤트가 관심 있는지 확인하는 비용은 이벤트를 호출할 때마다 지불해야 합니다. 특히 '마우스오버' 또는 '마우스이동'과 같이 자주 발생하는 이벤트의 경우 비용이 많이 들 수 있습니다.
일반적인 문제 및 해결 방법
$(document).ready
에서 하는 작업에 시간이 오래 걸립니다.
Malte의 개인적인 조언: $(document).ready
에서는 절대 아무것도 하지 마세요. 최종 형식으로 문서를 전송해 보세요. 좋습니다. 이벤트 리스너를 등록할 수 있지만 id-selector를 사용하거나 이벤트 위임을 사용해야 합니다. 'mousemove'와 같이 비용이 많이 드는 이벤트의 경우 필요할 때까지 등록을 지연합니다 (관련 요소의 마우스오버 이벤트).
실제 데이터를 가져오기 위한 Ajax 요청을 실행하는 등 작업을 정말로 해야 하는 경우에는 멋진 애니메이션을 표시합니다. 애니메이션 GIF인 경우 애니메이션을 데이터 URI로 포함하는 것이 좋습니다.
페이지에 플래시 영화를 추가한 이후로 모든 것이 느려졌습니다.
페이지에 Flash를 추가하면 항상 렌더링 속도가 약간 느려집니다. 창의 최종 레이아웃을 브라우저와 Flash 플러그인 간에 '협상'해야 하기 때문입니다. 페이지에 Flash를 완전히 배치할 수 없는 경우 'wmode' Flash 매개변수를 'window' 값(기본값)으로 설정해야 합니다. 이렇게 하면 HTML 및 Flash 요소를 합성하는 기능이 사용 중지됩니다. 즉, Flash 영화 위에 있는 HTML 요소를 볼 수 없으며 Flash 영화가 투명해질 수 없습니다. 불편을 끼쳐 드려 죄송하지만 이로 인해 실적이 크게 개선될 것입니다. 예를 들어 youtube.com에서 기본 영화 플레이어 위에 레이어를 배치하지 않는 방법을 살펴보세요.
localStorage에 항목을 저장하고 있는데 이제 애플리케이션이 끊김
localStorage에 쓰기는 하드 디스크를 스핀업하는 동기식 작업입니다. 애니메이션을 실행하는 동안 '장기 실행' 동기 작업을 실행하면 안 됩니다. localStorage에 대한 액세스를 사용자가 유휴 상태이고 애니메이션이 실행되지 않는 것으로 확인된 코드 위치로 이동합니다.
프로파일링 결과 jQuery 선택기가 매우 느림
먼저 선택자를 document.querySelectorAll을 통해 실행할 수 있는지 확인해야 합니다. JavaScript 콘솔에서 테스트할 수 있습니다. 예외가 있는 경우 JavaScript 프레임워크의 특수 확장 프로그램을 사용하지 않도록 선택기를 다시 작성합니다. 이렇게 하면 최신 브라우저에서 선택기의 속도가 훨씬 빨라집니다.
그래도 문제가 해결되지 않거나 최신 브라우저에서도 속도를 높이려면 다음 가이드라인을 따르세요.
- 선택 도구의 오른쪽에 최대한 구체적으로 입력하세요.
- 자주 사용하지 않는 태그 이름을 가장 오른쪽 선택기 부분으로 사용합니다.
- 아무것도 도움이 되지 않으면 id-selector를 사용할 수 있도록 코드를 다시 작성해 보세요.
이러한 모든 DOM 조작은 시간이 오래 걸립니다.
DOM 노드 삽입, 삭제, 업데이트가 여러 번 실행되면 속도가 느려질 수 있습니다. 일반적으로 큰 HTML 문자열을 생성하고 domNode.innerHTML = newHTML
를 사용하여 이전 콘텐츠를 대체하면 최적화할 수 있습니다. 이는 유지보수에 매우 좋지 않을 수 있으며 IE에서 메모리 링크를 만들 수 있으므로 주의하세요.
또 다른 일반적인 문제는 초기화 코드가 많은 HTML을 생성할 수 있다는 점입니다. 예를 들어 UX 권장사항을 모르는 디자인팀에서 원하는 대로 선택 상자를 여러 div로 변환하는 jQuery 플러그인이 있습니다. 페이지 속도를 높이려면 절대 이렇게 하지 마세요. 대신 서버 측에서 모든 마크업을 최종 형식으로 전송합니다. 이 방법에도 많은 문제가 있으므로 속도 향상을 위해 이러한 단점을 감수할 가치가 있는지 신중하게 생각해 보세요.
도구
- JSPerf - 작은 자바스크립트 스니펫 벤치마킹
- Firebug - Firefox에서 프로파일링
- Chrome 개발자 도구 (Safari에서 WebInspector로 사용 가능)
- DOM Monster - DOM 성능 최적화
- DynaTrace Ajax Edition - Internet Explorer의 프로파일링 및 페인트 최적화용