DOM의 개념과 조작 방법

Jin·2022년 3월 1일
0

Javascript

목록 보기
19/22

DOM (Document Object Model)은 HTML 요소를 Object처럼 다룰 수 있는 Model입니다.

우리는 Javascript를 사용한다면 DOM으로 HTML을 조작할 수 있습니다.

DOM을 조작할 수 있는 주요 메서드와 개념을 살펴보도록 하겠습니다.

Node vs. Element

Node는 DOM API 상에 존재하는 모든 것들입니다.

Element는 Node의 부분 집합으로 div나 span 같은 특정한 태그가 포함된 요소를 뜻합니다.

document.createElement(<< tagName >>)

const newDiv = document.createElement('div');

새로운 element를 만드는 메서드입니다.

DOM에 새로운 element가 생성되었지만 붕 떠 있는 상태입니다. 즉, DOM 트리의 어떠한 요소와도 연결되어 있지 않기 때문에 화면 상에 보이지 않습니다. newDiv를 추후에 DOM의 적절한 위치에 삽입하면 화면에 나타나게 됩니다.

append, appendChild

const newDiv = document.createElement('div');
newDiv.textContent = "Hi!"

document.body.append(newDiv);
document.body.appendChild(newDiv);

위와 같이 div에 텍스트를 추가하고 body에 append 혹은 appendChild를 하게 되면 body의 마지막 자식 요소로 추가되어 화면에 보이게 됩니다.

하지만, 화면에는 Hi! 가 2개가 아닌 하나만 보이게 됩니다. 그 이유는 특정 노드는 반드시 하나의 부모만을 가져야 하기 때문에 만약 하나의 노드로 여러 군데 append를 하게 되면 가장 마지막에 append한 부모 밑으로만 삽입됩니다. 이런 특성을 활용하여 특정 노드를 이동시켜야 할 때 굳이 삭제 후 append하지 않고 append만 하여도 되겠습니다.

append와 appendChild는 비슷해보이지만 차이점이 존재합니다.

append는 element나 문자열 모두 추가할 수 있지만 appendChild는 오로지 element만 추가할 수 있습니다.

또한, append는 리턴값으로 아무것도 반환하지 않지만 appendChild는 추가한 element를 반환합니다.

textContent vs. innerText vs. innerHTML

textContent는 콘텐츠를 text/plain으로 파싱한 결과이므로 파싱이 빨라서 성능이 좋습니다. 해당 노드가 가지고 있는 텍스트 값을 그대로 읽습니다.

innerText는 textContent와 비슷하지만 style 등의 마크업 언어가 적용된 상태입니다. 예를 들면, 어떤 element의 스타일에 display: none이 적용되어 있다면 그 안에 있는 innerText는 불러들이지 않습니다.

innerHTML은 text/html로 파싱한 결과로 파싱이 느리고 xss 공격에 취약합니다. 텍스트, 태그를 비롯하여 html이 허용하는 모든 것이 들어갈 수 있기 때문입니다. 특정 노드 자식 노드를 모두 리셋할 때에 innerHTML = "";를 사용하고 있다면 반복문을 돌리고 그 안에서 remove나 removeChild 메서드를 통해 하는 것이 좋습니다.

remove, removeChild

remove() 는 노드를 메모리에서 삭제하지만 removeChild()는 노드를 삭제하지 않습니다.

메모리에 해당 노드는 그대로 존재하며, 부모 노드와의 부모-자식관계를 끊어 DOM 트리에서 해제하는 것입니다. 최종적으로는 관계를 끊은 해당 노드의 참조를 반환합니다.

const newDiv = document.createElement('div');
newDiv.textContent = "Hi!"

document.body.append(newDiv);
document.body.appendChild(newDiv);

newDiv.remove(); // newDiv를 삭제
document.body.removeChild(newDiv); // 특정 노드의 자식인 newDiv를 삭제

querySelector, querySelectorAll

이 두 메서드에는 아주 중요한 규칙이 있습니다.

  • 태그명: 그대로
  • id명: #<< id명 >>
  • class명: .<< class명 >>

반드시 id와 class 이름을 사용해서 불러올 때는 # 또는 .을 앞에 추가하여야 합니다.

const newDiv = document.createElement('div');
newDiv.textContent = "Hi!"

document.body.append(newDiv);
document.body.appendChild(newDiv);

const oldDiv = document.querySelector("div");
console.log(oldDiv.textContent); // Hi!

querySelector는 만약 여러 개의 값이 조회할 경우에도 가장 먼저 조회된 노드 하나만을 불러옵니다.

여기서 '가장 먼저 조회된'이란 보통 HTML을 위에서 아래로 탐색하기 때문에 해당 조건을 만족하는 가장 위의 노드라고 보시면 됩니다.

querySelectorAll은 여러 개의 element도 다 같이 불러옵니다. 이를 배열 형태로 생각하고 forEach를 통해 순회할 수 있습니다.

하지만 엄밀히 말하면, querySelectorAll로 불러온 노드들이 담긴 것은 배열이 아닌 NodeList로 유사 배열입니다. 따라서, NodeList의 prototype에 정의되어 있는 메서드인 forEach는 바로 사용할 수 있지만 reduce같이 정의되어 있지 않은 메서드들은 바로 사용이 불가합니다.

유사 배열은 Array.from() 메서드를 통해 배열로 바꿀 수 있습니다.

classList

보통 인라인으로 스타일을 적용하기보다는 class에 스타일을 정의하고 이 클래스를 특정 노드에 추가 또는 삭제하는 방향으로 적용합니다. 그래야 관심사 분리를 통해 스타일과 로직을 분리하여 개발할 수 있기 때문입니다.

  • << Node >>.classList.add(<< class명 >>): 특정 노드에 클래스명을 추가합니다.
  • << Node >>.classList.remove(<< class명 >>): 특정 노드에 클래스명을 제거합니다.
  • << Node >>.classList.toggle(<< class명 >>): 특정 노드에 클래스명이 추가되어 있으면 삭제를, 추가되어 있지 않으면 추가합니다.

setAttribute

노드의 속성을 추가, 변경할 수 있습니다.

const button = document.querySelector("#registerButton");
button.setAttribute("disabled", "disabled");
button.removeAttribute("disabled");

만약, 특정 버튼을 가져와서 disabled 속성을 추가하고 싶으면 setAttribute를 위와 같이 사용하고, disabled 속성을 제거하고 싶다면 removeAttribute 메서드를 사용하면 됩니다.

children vs. childNodes

children은 자식 요소에 접근하고 childNodes는 자식 노드에 접근합니다.
children은 자식 요소가 포함된 HTMLCollection을 반환하며 비 요소 노드는 모두 제외됩니다.
childNodes는 자식 노드가 포함된 NodeLIst를 반환하며 요소 노드와 주석 노드 같은 비 요소 노드도 포함합니다.

DocumentFragment

DocumentFragment는 DOM의 단편적인 부분을 정의할 수 있는 노드입니다. 부모가 없는 최소화된 경량화된 문서 객체라고도 합니다.

DocumentFragment는 기본적으로 DOM과 동일하게 동작하지만, HTML의 DOM 트리에는 영향을 주지 않으며, 메모리에서만 정의됩니다.

이것은 document.createElement와 결정적인 차이를 가집니다. createElement 명령이 동작하면 당장 눈에 보이지는 않아도 DOM 어딘가에는 Element가 존재하게 됩니다. 하지만 DocumentFragment는 그렇지 않습니다.

위의 특성으로 Javascript 성능을 최적화할 수 있습니다. DocumentFragment에 변경이 일어나도 DOM에는 어떠한 변화도 일어나지 않기 때문에 브라우저가 화면을 다시 렌더링 하지 않습니다. 이것은 Reflow나 Repaint가 일어나지 않는다는 뜻이므로 불필요한 렌더링을 줄일 수 있습니다.

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

["code", "states", "frontend", "course"].forEach((text) => { 
  const liElement = document.createElement("li"); 
  liElement.textContent = text; 
  ulElement.appendChild(liElement); 
 });

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

참조
https://blogpack.tistory.com/683

profile
배워서 공유하기

0개의 댓글