Frontend/JS

JS - DocumentFragment 란?

Creative_Lee 2022. 10. 6. 16:37

우아한테크코스 5기를 준비하는 과정에서 프리코스 과제들을 접하게 됐습니다.

프리코스의 모든 과제들은 바닐라 자바스크립트로만 구현하여 제출해야하는데.....

바닐라 자바스크립트만으로 무언가를 만들어본 경험은

개발을 처음 배우기 시작했을 때 밖에 없는 것 같아 자신감이 떨어졌슴다!

 

아니나 다를까!
역대 지원자들의 코드를 찬찬히 살펴보던 중....

처음 보는 코드가 있더라구요...?

 

createDocumentFragment()  이게 무엇인고 ?
바로 공부 들어갔습니다!


0. DocumentFragment 에 대한 이해!

 

아래는 DocumentFragment에 대한 설명입니다.

DocumentFragment은 웹 문서의 메인 DOM 트리에 포함되지 않는,
가상 메모리에 존재하는 DOM 노드 객체입니다.
DocumentFragment 노드를 사용하면 메인 DOM 트리 외부에 경량화된 DOM을 만들 수 있어
브라우저 repaint 영향 없이 메모리에서 DOM 조작이 가능합니다.

 

무슨말인지 도통 이해가 가지 않으니, 하나씩 풀어서 이해해 봅시다!


1. createDocumentFragment( ) 로 DOM 요소 삽입하기!

 

DocumentFragment는 임시 바구니 입니다.

우리는 자바스크립트를 사용해 DOM 요소를 동적으로 추가할 수 있습니다.

이때 사용할 수 있는 방법중 하나가 바로 DocumentFragment  임시 바구니입니다.

 

임시 바구니로 DOM을 삽입해보면서,

DocumentFragment의 특징을 알아보겠습니다!


step 0. 목표

target-div에 3개의 <li> 태그를 자식으로 가지는 <ul> 태그를 추가하고 싶습니다.

DOM 접근은 최소한으로 하고싶습니다. ( 이유는 맨 뒤에 )


step 1.  바구니 만들기 

let target = document.querySelector('.target-div') // 목표물
let $바구니 = document.createDocumentFragment() // 임시 바구니 생성

우선 임시 바구니를 하나 만들었습니다.

바구니는 document.createDocumentFragment() 메서드로 만들 수 있습니다.

 

*DOM 트리에 포함되지 않는, 가상 메모리에 바구니가 생성 되었습니다.*  

이 바구니는 보이는 것 처럼 DOM 트리에 포함되지 않기 때문에, 
*여기에 무엇을 담든 DOM에 영향을 주지 않습니다.*

 


step 2.  바구니에 자식 담기

 

const target = document.querySelector('.target-div')
const $바구니 = document.createDocumentFragment()
const $ul = document.createElement('ul')

const liArray = [1, 2, 3]
liArray.forEach(num => {
  const $li = document.createElement('li')
  $li.textContent = num
  $ul.appendChild($li) // ul에 li 담기
})

$바구니.appendChild($ul) // 바구니에 ul 담기
console.log($바구니)

ul의 자식으로 li를 담았고 

바구니의 자식으로 ul을 담았습니다.

 

바구니를 출력해보면 다음과 같습니다.

바구니

*바구니에 무엇을 담던 여전히 DOM에는 영향이 없습니다.*


step 3.  target DOM에 바구니 넣기

 

const $target = document.querySelector('.target-div')
const $바구니 = document.createDocumentFragment()
const $ul = document.createElement('ul')

const liArray = [1, 2, 3]
liArray.forEach(num => {
  const $li = document.createElement('li')
  $li.textContent = num
  $ul.appendChild($li)
})

$바구니.appendChild($ul)

$target.appendChild($바구니) // 타겟에 바구니 넣기

이제 target-div에 바구니를 넣었습니다.
원하던 목표를 달성했습니다.

*그런데 바구니는 사라지고, 내용물만 담겨있네요!*

바구니는 없다.
ul 만 옮겨졌어요


step 4.  바구니 확인

 

$target.appendChild($바구니) // 담고나서~
console.log($바구니) // 출력하면?

이후에 바구니를 출력해보면?

*바구니도 비어있습니다*


바구니에서 내용물만 옮겨졌네요!
어찌됐든, 1번의 DOM 접근으로 목표를 달성했습니다.


2. 중간 점검

여기 까지의 DocumentFragment 특징을 정리해보면 다음과 같습니다.

 

*여기에 무엇을 담든 DOM에 영향을 주지 않습니다.*
*바구니에 무엇을 담던 여전히 DOM에는 영향이 없습니다.*

 - 최종적으로 DOM에 삽입 하기 전 까지는 아무 영향을 주지 않습니다.

 

*그런데 바구니는 사라지고, 내용물만 담겨있네요*
*바구니도 비어있습니다*

- DocumentFragment를 DOM에 삽입하면
  fragment를 제외한 자식 요소들만 삽입되고, 이후
fragment 객체는 빈 객체가 됩니다.

 


3. createElement( ) 로 DOM 요소 삽입하기!

 

위 예제에서는 DocumentFragment 바구니를 사용했지만,
createElement로 평범한 DIV 바구니를 만들어 사용해도
1번의 DOM 접근으로,
DOM에 삽입 하기 전 까지는 아무 영향을 주지 않으면서 
문제 해결이 가능합니다.

const $target = document.querySelector('.target-div')
const $DIV바구니 = document.createElement('div') // div 바구니~!
const $ul = document.createElement('ul')

const liArray = [1, 2, 3]
liArray.forEach(num => {
  const $li = document.createElement('li')
  $li.textContent = num
  $ul.appendChild($li)
})

$DIV바구니.appendChild($ul)

$target.appendChild($DIV바구니)

원하던 목표를 달성했습니다.

하지만 이번에는 바구니도 같이 들어가 있네요!


$target.appendChild($DIV바구니) // 담고나서~
console.log($DIV바구니) // 출력하면 ?

 

그리고 DIV 바구니에는 여전히 자식들이 담겨있습니다.


그냥 ul 태그를 바구니로 사용하면 해결되지 않나요? 

const $target = document.querySelector('.target-div')
const $ul바구니 = document.createElement('ul')  // ul 바구니~

const liArray = [1, 2, 3]
liArray.forEach(num => {
	const $li = document.createElement('li')
	$li.textContent = num
	$ul바구니.appendChild($li)
})

$target.appendChild($ul바구니)
 

네 실제로 해결된것 처럼 보이네요! 


$target.appendChild($ul바구니)
console.log($ul바구니)

여전히 ul 바구니에는 자식이 담겨 있긴 하지만요!


4. 만약 여러개의 루트 노드들을 한 번에 추가하고 싶다면?

그러면 이제 마지막으로 문제를 한번 바꿔보겠습니다.

target-div에 <ul>, <div>, <h3> 이렇게 3개의 루트 노트만 깔끔하게 추가하고 싶다면 어떻게 할까요?

 

createElement() 을 사용해서 DOM에 추가하면 무조건 바구니도 같이 들어갑니다.
깔끔하게 상위태그 없이 삽입하려면
어쩔 수 없이 3번 접근해서 DOM에 삽입해야겠네요. 

createDocumentFragment() 를 사용하면 어떨까요?
여전히 1번의 접근으로 DOM에 삽입가능합니다!

 


5. DocumentFragment 의 장점

이제 결론을 지어봅시다.


1. DocumentFragment는 브라우저 성능 최적화에 도움을 줍니다.

DOM을 수정하는 행위는 브라우저의 Reflow를 일으킵니다.
Reflow는 성능에 매우 큰 영향을 미치기 때문에 최대한 적게 발생시키는게 중요합니다.

 

즉 DOM에 요소를 여러개 삽입하더라도, 가능하다면 묶어서 한 번에 삽입하는 것이 좋습니다.

만약 위와 같은 상황이라면, DocumentFragment를 사용하는 것이 성능에 도움을 줄수 있겠죠?

 

 

2. DocumentFragment는 메모리 소비 걱정을 하지 않아도 됩니다.

위 예제에서 봤던것 처럼 DocumentFragment는 DOM에 삽입된 즉시 비어있게 됩니다. 
메모리에 대한 걱정을 하지 않아도 되는 이유입니다.


6. 성능상으로도 빠른가? 

그렇지 않습니다.
createElement    VS    createDocumentFragment

엔진 별로 차이가 있다고 하네요. 미미해서 의미도 없다합니다.

 

성능보단 가독성의 향상 측면에서 바라보는 것이 좋겠습니다! 

물론 Reflow가 여러 번 발생 될 수 있는 case라면 후자가 절대적으로 좋습니다!

 


7. 찐 결론

DOM에 요소를 삽입 할때에는

createDocumentFragment 메서드를 사용하자!

 

 

 

 

 

기본이 중요하다 !