Javascript - DocumentFragment를 사용해보자 [성능 최적화]

 Javascript는 브라우저가 정의하고 사용하는 DOM(Document Object Model)을 다룰 수 있습니다. DOM을 처음 접하시거나 아직은 잘 모르겠다고 생각하시는 분은 DOM을 HTML의 구조적인 정의를 다루는 요소라고 생각하시면 됩니다. HTML과 DOM을 구체적으로 정의 내리면 다음과 같습니다. HTML은 웹 페이지의 문서 구조를 생성하는 태그 시스템이고 DOM은 HTML이 가지는 문서 구조를 다룰 수 있는 인터페이스입니다.

HTML이 가지는 DOM의 계층형 트리 구조

 DOM은 HTML의 구조를 계층형 트리 형태로 정의합니다. 그리고 브라우저는 HTML이 가진 구조를 Style Sheet인 CSS를 사용해 화면을 그려 사용자가 쉽게 웹 페이지를 이용할 수 있도록 합니다. DOM은 원래 XML 문서를 정의하기 위한 인터페이스였지만, HTML에서도 사용할 수 있도록 확장되었습니다.

DocumentFragment의 정의  

 DocumentFragment는 DOM의 단편적인 부분을 정의할 수 있는 노드입니다. 전문적인 용어를 넣어 설명하면 부모가 없는 최소화된 경량화된 문서 객체라고도 이야기합니다. DocumentFragment는 기본적으로 DOM과 동일하게 동작하지만, HTML의 DOM 트리에는 영향을 주지 않으며, 메모리에서만 정의됩니다.

DocumentFragment 사용법

const documentFragment = new DocumentFragment();
// const documentFragment = document.createDocumentFragment();

const ulElement = document.createElement("ul");
documentFragment.appendChild(ulElement);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElement.appendChild(liElement);
});

console.log(documentFragment.textContent);
// onetwothreefourfive

 예제 코드는 new DocumentFragment() 생성자 함수를 DocumentFragment 객체를 생성합니다. 생성된 DocumentFragment 객체에 li Element를 만들어 추가합니다. new DocumentFragment() 생성자 함수 대신 document.createDocumentFragment() 함수를 사용할 수도 있습니다.

const divElement = document.createElement("div");

const ulElement = document.createElement("ul");
divElement.appendChild(ulElement);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElement.appendChild(liElement);
});

console.log(divElement.textContent);
// onetwothreefourfive

 일반적으로는 위의 예제 코드처럼 DocumentFragment 대신 포장할 수 있는 Element를 만들어 필요한 Element를 추가한 뒤 Body에 append 하는 방법으로 많이 사용합니다. 그렇다면 두 가지 방법의 차이점은 무엇일까요?

DocumentFragment는 사용하면 자동으로 이전된다.

const documentFragment = new DocumentFragment();

const ulElementOnFragment = document.createElement("ul");
documentFragment.appendChild(ulElementOnFragment);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElementOnFragment.appendChild(liElement);
});

document.body.appendChild(documentFragment);

console.log(documentFragment.textContent);
// ''

const divElement = document.createElement("div");

const ulElementOnDiv = document.createElement("ul");
divElement.appendChild(ulElementOnDiv);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElementOnDiv.appendChild(liElement);
});

document.body.appendChild(divElement);

console.log(divElement.textContent);
// onetwothreefourfive

 예제 코드는 DocumentFragment를 사용한 결과와 div Element를 사용한 결과를 보여줍니다. DocumentFragment를 body에 추가하면 DocumentFragment에는 남아있는 내용이 없습니다. div Element를 사용한 경우에는 body에 추가 하기 전과 후가 div Element가 동일합니다.

DocumentFragment는 DocumentFragment는 추가하지 않는다.

const documentFragment = new DocumentFragment();

const ulElement = document.createElement("ul");
documentFragment.appendChild(ulElement);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElement.appendChild(liElement);
});

document.body.appendChild(documentFragment);

DocumentFragment를 사용한 DOM 수정

 예제 코드를 통해 만들어진 화면을 확인하면 다음과 같습니다. DOM 트리에서 DocumentFragment 요소는 확인할 수 없습니다. DocumentFragment의 내용만 반영된 결과를 볼 수 있습니다.

const divElement = document.createElement("div");

const ulElement = document.createElement("ul");
divElement.appendChild(ulElement);

["one", "two", "three", "four", "five"].forEach((text) => {
    const liElement = document.createElement("li");
    liElement.textContent = text;
    ulElement.appendChild(liElement);
});

document.body.appendChild(divElement);

Element를 사용한 DOM 수정

 Element를 사용해서 DOM을 수정하면 포장으로 사용한 div Element도 함께 반영된 결과를 확인할 수 있습니다.

 

 

그렇다면 성능 최적화는 무슨 이야기일까?

 DocumentFragment는 활성화된 DOM의 일부가 아닙니다. 처음에 이야기한 것처럼 DocumentFragment는 DOM에 반영하기 전까지는 메모리상에서만 존재합니다. 즉 DocumentFragment에 변경이 일어나도 DOM의 구조에는 변경이 일어나지 않기 때문에 브라우저가 화면을 다시 랜더링 하지 않습니다. 이 말은 Reflow나 Repaint가 일어나지 않는다는 말과도 같습니다.

 

 Element를 이용해 수정을 반영하면 수정이 일어난 횟수만큼 화면을 다시 그리는 작업이 실행됩니다. 최근의 브라우저는 화면 랜더링이 최적화가 잘 되어 이전처럼 n번의 수정만큼 다시 그리지는 않지만 여전히 Reflow나 Repaint가 일어나는 것은 사실입니다.

 

 DocumentFragment는 메모리상에서만 만들어지고 화면에 영향을 주지 않기 때문에 n번의 수정이 일어나도 반영을 한 번만 한다면 Reflow나 Repaint는 한 번만(정확히 한 번만 일어나지는 않습니다.) 일어납니다. 

innerHTML VS DocumentFragment 속도 차이

 번외의 성격으로 innerHTML과 DocumentFragment를 사용했을 때 어느 쪽이 더 빠른 속도를 보여줄까요? 두 가지 방법의 성능을 측정하는 사이트가 있어 쉽게 결과를 확인할 수 있었습니다. 사이트를 통해 다음과 같은 결과를 확인할 수 있습니다.

innerHTML VS DocumentFragment 성능 테스트 결과

  innerHTML이 DocumentFragment에 비해 빠른 속도를 보여줍니다. 이 부분은 단순히 값만을 기준으로 측정한 사항이므로 실제로 개발되고 사용하는 코드와는 다릅니다. 프로젝트에서는 요구조건에 따라 기능이 동적으로 정의되는 경우가 많습니다. 단순히 Element를 넣고, 빼고 하는 것뿐이 아니라 속성을 정의하고 class를 추가하는 등 여러 가지 작업을 합니다.

 

 즉 성능 측정 결과가 있다고 알려드리는 부분일 뿐 성능 최적화를 위해서는 DocumentFragment를 사용하는 것이 좋습니다. 브라우저가 화면을 랜더링 할 때 이야기되는 Reflow나 Repaint 같은 이슈는 단순히 값을 넣고 빼는 문제가 아닙니다. 이 성능 결과는 이런 결과가 있다 정도로만 생각해주세요.

반응형

댓글

Designed by JB FACTORY