본문으로 건너뛰기
2022. 5. 19·약 5분

DocumentFragment는 언제 쓰면 좋을까요?

기울어진 카드에 담긴 파란 원·네모·세모가 분홍 더하기 표시를 지나 문서 트리의 자식으로 붙는 그림

개발 스터디에서 라이브러리나 프레임워크 없이 Observer 패턴으로 상태를 관리하고, Virtual DOM의 개념을 단순하게 재현한 MVC 앱을 만들었습니다. 렌더 함수를 구현하며 여러 DOM 노드를 조립하는 방법을 찾다가 DocumentFragment를 알게 되었습니다. 이 글에서는 일반 요소를 임시 컨테이너로 쓰는 방법과 무엇이 다른지 비교합니다.

요약​

DocumentFragment의 정의​

DocumentFragment는 문서 트리에 연결되지 않은 DOM 노드들을 임시로 담는 객체입니다. 이 안에서 새 노드를 조립해도 화면에 표시된 문서에는 영향을 주지 않습니다.

용도와 효과​

  • 여러 노드로 이루어진 트리를 문서 밖에서 조립한 뒤 한 번의 호출로 옮길 때 사용합니다.

  • 여러 형제 노드를 담을 수 있어 불필요한 부모 요소를 추가하지 않아도 됩니다.


개발자 도구에서 확인한 객체는 다음과 같습니다.

콘솔에 펼친 DocumentFragment 객체. nodeType 은 11, childElementCount 는 0 으로 표시된다


여러 노드 삽입하기​

JavaScript DOM API로 200개의 목록 항목을 문서에 삽입한다고 가정해 보겠습니다.

노드를 하나씩 삽입하는 예

(function () {
const targetNode = document.querySelector("ul"); // 새로운 노드 트리를 집어넣을 곳

for (let i = 0; i < 200; i++) {
const li = document.createElement("li");
li.innerText = i + "번째 리스트입니다.";
targetNode.append(li);
}
})();

이 코드는 문서에 연결된 ul의 append를 200번 호출합니다.

li 를 200개 붙여 만든 목록이 화면에 렌더링된 결과

다만 이런 동기 루프 안에서 append를 반복해도 브라우저가 렌더링을 모아서(batching) 처리하므로 reflow/repaint가 실제로 n번 일어나지는 않습니다. MDN에서도 DocumentFragment의 성능상 이점은 과장된 면이 있다고 명시하고 있습니다. 그럼에도 트리를 미리 조립해 한 번에 삽입하는 방식은 DOM 접근이 줄고 코드의 의도도 분명해집니다.

따라서 이 예제에서 중요한 것은 DocumentFragment 자체보다 문서 밖에서 트리를 조립한 뒤 한 번에 붙이는 구조입니다. DocumentFragment 없이도 문서 밖의 ul에 항목을 먼저 붙인 뒤 한 번에 삽입할 수 있습니다.

(function () {
const targetNode = document.querySelector("#target"); // 다른 타겟 노드를 잡습니다

const ul = document.createElement("ul");

for (let i = 0; i < 200; i++) {
const li = document.createElement("li");
li.innerText = i + "번째 리스트입니다.";
ul.append(li);
}

targetNode.append(ul);
})();

DocumentFragment를 사용하면 같은 과정을 다음처럼 작성할 수 있습니다.

(function () {
const targetNode = document.querySelector("#target");

const fragment = new DocumentFragment();
const ul = document.createElement("ul");

for (let i = 0; i < 200; i++) {
const li = document.createElement("li");
li.innerText = i + "번째 리스트입니다.";
ul.append(li);
}

fragment.append(ul);
targetNode.append(fragment);
})();

결과는 같지만 fragment 자체는 문서에 남지 않고 ul과 그 자식 li만 삽입됩니다.

개발자 도구 Elements 패널. section 아래에 ul 만 들어가고 fragment 자체는 보이지 않는다


일반 요소와 무엇이 다를까?​

일반 요소를 임시 부모로 쓰는 방법과 fragment를 쓰는 방법의 차이를 살펴보겠습니다.

첫 번째 차이점: 여러 루트 노드를 한 번에 삽입하기​

불필요한 부모 요소를 추가하지 않고 여러 형제 노드를 한 번에 삽입할 수 있습니다.

DocumentFragment를 삽입하면 자식 노드들이 대상 문서로 이동합니다. fragment 객체는 사라지는 것이 아니라 자식이 없는 상태로 남습니다.

아래 코드에서는 형제 노드를 각각 삽입하므로 문서에 연결된 대상 노드의 append를 세 번 호출합니다. append에 여러 노드를 인자로 전달하면 이 작업도 한 번의 호출로 처리할 수 있습니다.

(function () {
const targetNode = document.querySelector("#target");

targetNode.append(document.createElement("div")); // 첫번째 접근
targetNode.append(document.createElement("h1")); // 두번째 접근
targetNode.append(document.createElement("p")); // 세번째 접근
})();

DOM 에 세 번 접근해 div, h1, p 를 각각 붙인 뒤의 Elements 패널

Fragment를 사용하면 형제 노드를 먼저 모은 뒤 한 번의 append 호출로 같은 결과를 만듭니다.

(function () {
const targetNode = document.querySelector("#target");
const fragment = new DocumentFragment();

fragment.append(document.createElement("div"));
fragment.append(document.createElement("h1"));
fragment.append(document.createElement("p"));

targetNode.append(fragment); // 한 번만 DOM에 접근합니다
})();

fragment 로 한 번만 접근해 같은 div, h1, p 가 들어간 Elements 패널


두 번째 차이점: 성능​

두 방식의 실행 성능도 비교해 보았습니다.

createDocumentFragment 와 createElement 중 무엇을 쓸지 묻는 스택오버플로우 질문

벤치마크 테스트​

createElement와 createDocumentFragment로 각각 루트 컨테이너를 만들고, 10,000개의 노드를 담아 문서에 삽입하는 벤치마크를 실행했습니다.

measurethat.net 에 만든 createElement 대 createDocumentFragment 벤치마크 설정 화면

이 실행에서는 createElement를 사용한 경우가 61.98 ops/sec로 더 빨랐습니다.

벤치마크 결과 표. createElement 가 61.98 ops/sec 로 가장 빠르게 나왔다

같은 벤치마크의 막대그래프. createElement 쪽 막대가 더 높다

다른 실행에서도 createElement 쪽이 더 빨랐습니다.

다른 벤치마크에서도 createElement 가 초당 546.319회로 앞서는 막대그래프

하지만 이 결과는 실행 환경에 따라 달라졌습니다.

환경에 따른 성능 차이​

브라우저와 OS 별로 실행된 벤치마크 결과 목록

Windows와 Android의 Chrome에서는 createDocumentFragment가 더 빨랐습니다. 결과는 운영체제와 브라우저 버전뿐 아니라 벤치마크 구성에도 영향을 받으므로 이 측정만으로 어느 방식이 항상 빠르다고 결론 내릴 수는 없습니다.

윈도우 크롬 95 결과. createDocumentFragment 가 40.9 ops/sec 로 더 빠르다

안드로이드 크롬 모바일 91 결과. createDocumentFragment 가 318.3 ops/sec 로 더 빠르다


세 번째 차이점: 메모리 관리​

문서에서 분리된 DOM 노드를 detached DOM 노드라고 합니다. 분리 자체가 메모리 누수는 아닙니다. 더 이상 쓰지 않는 노드를 도달 가능한 자바스크립트 객체가 계속 참조하면 가비지 컬렉터(GC)가 회수하지 못해 누수가 생길 수 있습니다.

Fragment는 삽입 후 자식 노드가 없는 상태가 됩니다. 하지만 다른 곳에서 그 노드를 계속 참조할 수 있으므로 이것만으로 메모리 누수를 막지는 못합니다.

힙 스냅샷으로 detached DOM 트리 메모리 누수를 찾는 방법을 설명한 크롬 개발자 문서

따라서 fragment를 썼다는 사실만으로 메모리 누수를 피할 수 있다고 볼 수 없습니다. 누수가 의심된다면 힙 스냅샷에서 분리된 노드를 계속 참조하는 객체가 있는지 확인해야 합니다.


결론​

  • DocumentFragment는 불필요한 부모 요소 없이 여러 형제 노드를 조립해 삽입할 때 유용합니다.
  • 동기 루프 안의 append도 브라우저가 렌더링을 모아 처리하므로, 성능 차이는 흔히 알려진 것만큼 크지 않을 수 있습니다.
  • 일반 요소와 DocumentFragment의 성능은 환경과 측정 방법에 따라 달라질 수 있으므로 실제 사용 환경에서 확인해야 합니다.

References​



좋은 사람들과 재미있는 일을 하며 열정적이고 즐겁게 살고 싶은 개발자