DocumentFragment와 리플로우


DocumentFragment?

DocumentFragment 인터페이스는 부모가 없는 아주 작은 document 객체를 나타냅니다. Document의 경량화된 버전으로 사용되며 표준문서와 같이 노드로 구성된 문서 구조의 일부를 저장합니다. 중요한 차이점은 DocumentFragment는 활성화된 문서 트리 구조의 일부가 아니기 때문에 fragment를 변경해도 문서에는 영향을 미치지 않으며(reflow 포함) 성능에도 영향이 없다는 점입니다. - MDN

여기서 중요한 점은 DocumentFragmentdocument객체라는 점. 즉, 일반적인 DOM노드 처럼 노드로 구성되어 문서 구조를 저장한다는 것이다. 다만, 차이점이라면 경량화되어 있다는 점과, Tree 구조의 일부가 아니기 때문에, 문서에 영향을 끼치지 않는다는 점이다.

경량화, Tree 구조의 일부가 아니다??

이게 무슨 말인지 예제를 통해 알아보자


DocumentFragment 활용

복수의 노드 생성과 추가

1. createElement() 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
<ul id="fruits"></ul>

<script>
// DOM Node
const $fruits = document.getElementById('fruits');

['Apple', 'Banana', 'Orange'].forEach(text => {
const $li = document.createElement('li');
const textNode = document.createTextNode(text);
$li.appendChild(textNode);
$fruits.appendChild($li);
});
</script>
</body>

위 예제는 <ul> 요소에 <li> 요소들을 동적으로 생성하여 append하는 예제이다.

위와 같이 동적으로 요소를 추가해주면, 리플로우(reflow)가 일어나게 된다.


리플로우(reflow)?

WebKit Browser rendering workflow
DOM API등으로 인한 DOM(Document Object Model) Tree, CSSOM(CSS Object Model)에 변경이 생기게 되면, 브라우저는 변경을 감지하고, 브라우저를 렌더링하는데 필요한 결과물인 Render Tree를 재구성하게 된다. 이때, 레이아웃(요소의 위치나 크기)에 변경이 생기게 되면, 요소들을 재배치해야 하는데, 요소를 배치하는 Layout과정이 다시 일어나게 되는 것을 바로 리플로우(reflow)라고 한다.

리플로우는 렌더링과정에서 가장 큰 리소스를 필요로 하는 작업으로 웹/앱의 성능상 최소화해야 한다.

(Firefox의 렌더링 엔진인 Gecko 같은 경우는 Layout과정 자체를 reflow라고 명명한다.)


위 예제의 경우는 요소가 추가될 때마다, 화면의 레이아웃이 바뀌어야 하므로,

(실제로는 브라우저가 최적화를 하겠지만?) 이론상 3번의 리플로우가 일어나게 된다.

그렇다면, 소개하고자 하는 DocumentFragment의 경우는 어떨까?


2. createDocumentFragment()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<ul id="fruits"></ul>

<script>
// DOM Nodes
const $fruits = document.getElementById('fruits');
const $fragment = document.createDocumentFragment();

['Apple', 'Banana', 'Orange'].forEach(text => {
const $li = document.createElement('li');
const textNode = document.createTextNode(text);
$li.appendChild(textNode);
$fragment.appendChild($li);
});
$fruits.appendChild($fragment);
</script>
</body>

위 예제는 1번의 <li>요소 동적추가 방식과 동일해 보인다.

다른 점이라면, 바로 <ul>의 자식으로 추가하는 것이 아니고,

document.createDocumentFragment()메서드로 DocumentFragment객체를 생성하고,

생성한 DocumentFragment객체에 <li>요소들을 자식 요소들로 추가한 후, 완성된 DocumentFragment객체를 <ul>의 자식으로 추가한다.


그래서 뭐..?

결론적으로 이렇게함으로써 리플로우는 1번만 일어나게 된다.

만약, 다 수의 규모가 큰 노드를 동적으로 생성한다고 하면, 웹/앱의 성능상 큰 장애요소가 될 수 있다.


그럼 똑같이 div에 추가해서 div를 append 하면 되지 않아..?

documentFragment경량화된 docuement객체이자, DOM Tree 구조의 일부가 아니다라고 했다.

documentFragment를 이용하여, DOM 요소를 추가하면, 다른 방법에 비해 속도도 빠르고, DOM에 렌더링되지 않는다는 장점이 있다.

documentFragment 이용

위 스크린샷을 보면 documentFragment가 DOM Tree에 포함되지 않아 렌더링 되지 않은 것을 볼 수 있다.



호환성

DocumentFragment 노드는 IE9+에서 정상동작한다.

왠만해서는 사용하는데 문제가 없다.

다만 DocumentFragment() 생성자는 IE에서 지원되지 않는다.

IE 호환성을 생각한다면, document.createDocumentFragment()를 사용해서 생성해야 한다.



결론

물론 이를 사용하지 않아도, 모던한 SPA프레임워크를 사용하면 Virtual DOM을 통해 리플로우를 최소화해준다.

하지만, 프레임워크는 기존 작업을 편하게 하기 위해 사용하는 것이지,

개인적으로 프레임워크 자체에 종속되어 개발하는 것은 좋지 않다고 생각한다.

프레임워크를 사용하기에 앞서 근간이 되는 Vanilla JS를 알고, 프레임워크 없이도 최적화된 개발을 할 수 있어야 한다고 생각한다.

(이래놓고 신나서 프레임워크 쓰겠지..😇)



참고