[자바스크립트] HTML Element 추가 방법과 DocumentFragment 노드

이윤우·2023년 10월 13일
0

JavaScript

목록 보기
34/34
post-thumbnail

자바스크립트로 DOM을 조작하다보면 가장 번잡한 일이 새로운 HTML Element를 만들어 추가하는 것입니다. DOM 조작에 의해 DOM에 새로운 노드가 추가되거나 삭제되면 리플로우와 리페인트가 발생하는 원인이 되므로 성능에 영향을 줍니다. 따라서 복잡한 콘텐츠를 다루는 DOM 조작은 성능 최적화를 위해 주의해서 다뤄야 합니다. 이번 게시글을 통해 HTML Element를 추가하는 방법들과 각 방법의 장단점과 활용 방안에 대해 정리해보겠습니다.

innerHTML 프로퍼티 (자식 노드 재할당)

Element.prototype.innerHTML 프로퍼티에 문자열을 할당하면 요소 노드의 모든 자식 노드가 제거되고 할당된 문자열에 포함되어 있는 HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영됩니다.

const $app = document.getElementById('app');

// HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 파싱
$app.innerHTML = `<li>할 일</li>`;
  • 장점

    • HTML 마크업 문자열로 간단히 DOM 조작 가능
  • 단점

    • 모든 자식 노드를 제거하고 할당(중간에 삽입 불가능)

    • 크로스 사이트 스크립팅 공격에 취약

      크로스 사이트 스크립팅 공격
      크로스 사이트 스크립팅 공격은 공격자가 상대방의 브라우저에 스크립트가 실행되도록 해 사용자의 세션을 가로채거나, 웹 사이트를 변조하거나, 악의적 콘텐츠를 삽입하거나, 피싱 공격을 진행하는 것을 말합니다.

      const $app = document.getElementById('app');
      
      // 에러 이벤트를 강제로 발생시켜서 자바스크립트 코드가 실행
      $app.innerHTML = `<img src="x" >`;

      링크: https://codesandbox.io/embed/1-innerhtml-5y8m2x?fontsize=14&hidenavigation=1&theme=dark

  • 활용 방안

    • 복잡하지 않은 요소를 새롭제 추가할 때는 유용하지만 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입해야 할 때는 사용하지 않는 것이 좋습니다.

insertAdjacentHTML 메소드

Element.prototype.insertAdjacentHTML(position, DOMString) 메소드는 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입합니다. insertAdjacentHTML()는 두 번째 인자로 전달한 HTML 마크업 문자열을 파싱하고 그 결과로 생성된 노드를 첫 번째 인수로 전달한 위치에 삽입하여 DOM에 반영합니다. 첫 번째 인수로 전달받은 위치는 아래 그림과 같습니다.

const $app = document.getElementById('app');

const childHtmlString = `<li>할 일</li>`
// HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 추가
$app.insertAdjacentHTML('beforeend', childHtmlString)
  • 장점
    • HTML 마크업 문자열로 간단히 DOM 조작 가능
    • 기존 요소에는 영향을 주지 않고 새롭게 삽입될 요소만을 파싱하여 추가
  • 단점
    • innerHTML 과 마찬가지로 HTML 마크업 문자열을 파싱하므로 크로스 사이트 스크립팅 공격에 취약
  • 활용 방안
    • HTML 마크업 문자열로 기존 요소에 새로운 노드를 추가할 떄 사용 가능합니다.

appendChild 메소드

Node.prototype.appendChild(childNode) 메소드는 매개변수 childNode에게 인수로 전달한 노드를 appendChild 메소드를 호출한 노드의 마지막 자식 노드로 추가합니다. appendChild() 는 노드 객체만 추가할 수 있고, 문자열을 추가하면 에러가 발생합니다. 그리고 return 값을 반환합니다. 또, 한번에 2개 이상의 요소를 추가할 수 없습니다.

const $app = document.getElementById('app');

// 자식 노드를 추가하고 기존 DOM에 추가하려는 경우
const $li1 = document.createElement('li');
const $childNode = document.createElement('input');
$childNode.setAttribute('type', 'hidden');
$childNode.setAttribute('value', 100);
// 새롭게 생선한 요소에 자식 노드 추가 (DOM 변경 X)
$li1.appendChild($childNode);
// 기존 DOM에 한번 추가하므로 DOM은 한 번 변경된다.
$app.appendChild($li1);

// 텍스트 추가 불가능
$app.appendChild('<li>할 일2</li>');

// 2개 이상의 요소 추가 불가능 (인자를 2개 넣어도 첫 번째 인자만 부모 노드에 추가)
const $li2 = document.createElement('li');
li2.textContent = '할 일2';
const $li3 = document.createElement('li');
li3.textContent = '할 일3';
$app.appendChild($li2, $li3);

  • 장점
    • 크로스 사이트 스크립팅 공격으로부터 안전
    • 기존 요소에는 영향을 주지 않고 새롭게 삽입될 요소만을 파싱하여 추가
    • return 반환
    • IE 지원
  • 단점
    • HTML 마크업 문자열을 사용할 수 없음
    • 한번에 1개의 노드객체만 추가할 수 있음
  • 활용 방안
    • 1개의 노드객체를 추가할 때 사용할 수 있습니다.

append 메소드

Element.append() 메소드는 appendChild() 와 같은 비슷한 기능을 수행합니다. 하지만 문자열을 입력받아 텍스트 노드를 생성해 추가할 수 있습니다. 또, 한번에 2개 이상의 자식 노드를 추가할 수 있습니다. appendChild()에 비해 기능이나 확장성 면에서 더 뛰어나다고 생각합니다.

const $app = document.getElementById('app');

// 기본 형태
const $li1 = document.createElement('li');
$li1.textContent = '할 일 1';
const $li2 = document.createElement('li');
$li2.textContent = '할 일 2';
$app.append($li1, $li2);

// 구조분해할당 사용
const todoNodeList = ['할 일 3', '할 일 4'].map((todo) => {
	const $li = document.createElement('li');
  	$li.textContent = todo;
  	return $li;
})
$app.append(...todoNodeList)
  • 장점
    • 크로스 사이트 스크립팅 공격으로부터 안전
    • 기존 요소에는 영향을 주지 않고 새롭게 삽입될 요소만을 파싱하여 추가
    • 문자열을 입력받아 텍스트 노드를 생성해 추가할 수 있음
    • 한번에 2개 이상의 노드객체를 받아 추가할 수 있음
  • 단점
    • return 을 반환하지 않음
    • IE 지원하지 않음
  • 활용 방안
    • 2개 이상의 노드객체를 추가하려고 할 때 사용할 수 있습니다.

DocumentFragment

Documnet.prototype.DocumentFragment 노드는 웹 문서의 메인 DOM 트리에 포함되지 않는, 가상의 메모리에 존재하는 DOM 노드 객체입니다. 이를 통해 메인 DOM 트리 외부에 경량화된 DOM을 만들 수 있어 리플로우와 리페인트의 영향 없이 메모리에서 DOM 조작이 가능합니다. 여러 개의 요소 노드를 생성하여 DOM에 추가하려고 할 때 유용하게 사용될 수 있습니다.

const $app = document.getElementById('app');

// DocumentFragment 노드 생성
const $fragment = document.createDocumentFragment();
const todoNodeList = ['할 일 1', '할 일 2'].forEach((todo) => {
	const $li = document.createElement('li');
  	$li.textContent = todo;
  
  	// $li 요소 노드를 DocumentFragment 노드의 마지막 자식 노드로 추가
  	$fragment.appendChild($li);
})

// DocumentFragment 노드를 $app 요소 노드의 마지막 자식 노드로 추가
$app.appendChild($fragment);
  • 장점
    • 여러 요소 노드를 추가하지만 실제로 DOM 변경이 발생하는 것은 한 번뿐이며 리플로우와 리페인트도 한 번만 실행됩니다.
  • 활용 방안
    • 여러 개의 요소 노드를 DOM에 추가하는 경우 DocumentFragment 노드를 사용하는 것이 더 효율적입니다.

0개의 댓글