들어가기 전에
자바스크립트는 오늘날 웹 서비스 개발에서 뗄레야 뗄 수 없는 언어이다. 자바스크립트를 통하여 누구나 손쉽게 문서 객체 모델 *(Document Object Model, 이하 DOM)*을 자유롭게 조작하여 역동적인 웹 페이지를 만들 수 있다. 이렇게 대부분의 웹 사이트에서 사용하고 있는 스크립트를 사용한 DOM의 조작 기능에는 성능상의 문제가 있다. 이 글에서는 스크립트를 사용한 DOM 조작 시 발생하는 성능저하 문제점과 그 최적화에 대해 알아본다.
DOM의 빠른 갱신 시 발생하는 성능 문제
낮은 버전의 브라우저 특히 IE 에서는 스크립트를 사용하여 DOM을 빠르게 갱신하는 경우 심각한 성능저하를 발생시킨다. 아직까지 각 사이트에서 많이 사용되는 IE7을 기준으로 예제를 작성하였다.
var targetTb = document.getElementById(tbl_id);
var rowLength = targetTb.rows.length - 1;
for ( var i = 1; i < rowLength; i += 2) {
preCell = targetTb.rows[i].cells[0];
currCell = targetTb.rows[i + 1].cells[0];
preCell.rowSpan = preCell.rowSpan + currCell.rowSpan;
currCell.parentNode.deleteCell(0);
}
<예제1> 테이블 병합
이 스크립트는 테이블을 읽어 첫번째 열을 2개씩 묶어 병합하는 스크립트 코드이다. 위 예제를 통해 IE7에서의 성능저하를 체감하기 위해서는 CSS 적용이 필요하다. 눈에 띄는 결과를 보여줄 수 있는 테스트를 위해 2개의 열과 1000개의 행을 가진 테이블을 준비하였으며 아래와 같은 CSS를 설정하였다.
td, th{font-size: 13px;border: 1px solid black;}
th{background-color: #000000; height:40px;color: white;}
td{background-color: #EEEEEE; height:20px;color: black;}
table{border: 1px solid black;border-collapse:collapse;}
<예제1-1> 예제 CSS
브라우저 | 병합 소요시간(초) |
---|---|
IE 7 | 8.852s |
IE 8 | 0.224s |
IE 9 | 0.217s |
Fire Fox (25) | 0.010s |
Chrome (30) | 0.014s |
<표1> 테이블 병합 스크립트[예제1] 브라우저 별 소요시간
위 예제의 결과로 IE7에서의 큰 성능저하를 볼 수 있다. 먼저 원인을 짚고 넘어가보자. 성능 저하의 원인은 스크립트가 DOM을 수정함으로써 생기는 리플로우(reflow)에 있다. 리플로우란 브라우저가 DOM요소들을 다시 렌더링하는 과정을 말하는데 최신 브라우저는 DOM이 빠르게 갱신될 때 과도한 렌더링으로 인한 소요시간 증가를 막아주지만 IE7은 그렇지 않다. 예제에서는 500번의 열 크기 변환과 500번의 열 삭제 총 1000번의 리플로우가 있었다. 이러한 많은양의 리플로우가 발생하는 경우 브라우저는 큰 성능저하를 보여주며 웹 페이지의 DOM이 복잡하게 구성되어 있고 CSS가 많이 적용된 사이트일수록 더욱 심해진다.
이 문제의 해결책은 바로 DocumentFragment를 사용하는 것이다. DocumentFragment는 화면상에 표시되지 않는 document-like한 컨테이너로 DOM이 갱신될 때 마다 과도한 리플로우를 하지 않도록 한다
var targetTb = document.getElementById(tbl_id);
var frag = document.createDocumentFragment();
frag.appendChild(targetTb);
var rowLength = targetTb.rows.length - 1;
for ( var i = 1; i < rowLength; i += 2) {
preCell = targetTb.rows[i].cells[0];
currCell = targetTb.rows[i + 1].cells[0];
preCell.rowSpan = preCell.rowSpan + currCell.rowSpan;
currCell.parentNode.deleteCell(0);
} // 조작된 DOM을 원래 위치에 넣는다.
document.getElementById('test').appendChild(frag.cloneNode(true));
<예제2>DocumentFragment를 사용한 리플로우를 최소화 한 DOM 갱신
브라우저 | 병합 소요시간(초) |
---|---|
IE 7 | 3.647s |
IE 8 | 0.166s |
IE 9 | 0.044s |
Fire Fox (25) | 0.008s |
Chrome (30) | 0.006s |
<표2> – 브라우저 별 병합 스크립트 소요시간 2
1000회의 리플로우 횟수를 2회로 줄인 결과 예제 스크립트는 IE7에서 기존 코드 대비 약 3배의 성능향상이 있었다. 다른 브라우저도 컴퓨터 성능에 따라 약간씩 다르지만 평균적으로 2배의 성능향상을 보여준다. IE에서 가장 성능저하가 매우 심한 테이블로 예제를 작성하였으나 리플로우를 줄임으로써 얻는 최적화 방식은 IE만이 아닌 모든 브라우저에서 스크립트로 DOM을 조작하는 모든 영역에서 활용할 수 있는 방식임을 예제를 통해 알 수 있다.
DocumentFragment는 본래 용도인 생성에도 빠른 성능을 보여준다.
var div = document.getElementsByTagName("div");
for ( var i = 0; i < div.length; i++) {
for ( var e = 0; e < elems.length; e++) {
div[i].appendChild(elems[e].cloneNode(true));
}
}
<예제3-1> appendChild를 사용한 DOM 그리기
var div = document.getElementsByTagName("div");
var frag = document.createDocumentFragment();
for ( var e = 0; e < elems.length; e++) {
frag.appendChild(elems[e]);
}
for ( var i = 0; i < div.length; i++) {
div[i].appendChild(frag.cloneNode(true));
}
<예제3-2> documentFragment를 사용한 DOM 그리기
브라우저 | 예제3-1 소요시간(초) | 예제3-2 소요시간(초) |
---|---|---|
IE 7 | 0.201s | 0.080s |
IE 8 | 0.104s | 0.030s |
IE 9 | 0.044s | 0.020s |
Fire Fox (25) | 0.005s | 0.002s |
Chrome (30) | 0.003s | 0.000s |
<표2> 브라우저별 DOM 그리기 소요시간
단순한 DOM 변경에도 평균적으로 2배의 성능향상이 있었다.
마치며
DocumentFragment을 사용한 DOM 조작 스크립트 최적화 방법은 웹 개발에서 흔하게 무시되곤 하는 영역이다. 최신 브라우저의 경우 리플로우가 발생하지 않도록 최적화되고 성능이 향상되어 그 차이가 체감되지 않기 때문이다. 그러나 ActiveX 기반의 국내 웹 환경상 낮은 버전의 IE를 요구하는 경우가 분명히 존재하며 그런 환경이 아니더라도 많은 DOM을 조작해야만 하는 역동적인 화면에서의 브라우저의 부담을 덜어주고 빠른 화면갱신을 위해서 DocumentFragment를 활용하면 충분한 최적화를 얻을 수 있을 것이다.
브라우저별 DocumentFragment 사용과 미사용 성능차이