[HOW]DOM의 노드 조작 방법

HOW

 ⋅ Jan 23, 2024 ⋅ 5 min read

How to operate DOM's Node
How to operate DOM's Node

웹 화면을 구성하는 HTML의 요소를 동적으로 변경하기 위해서는 어떻게 해야 할까요?

이를 위해서는 DOM의 노드 객체에 접근하여 우리가 원하는 내용과 구조로 조작해야 합니다. 지금부터 DOM의 노드 객체에 접근하여 조작하는 방법에 대해 알아보도록 하겠습니다!

1. 노드 접근

HTML 요소를 변경하거나 삭제하기 위해서는 우선 요소에 접근해야 합니다. 따라서, DOM은 노드에 접근하기 위한 다양한 방법을 제공합니다.

  1. Document.getElementById()

    Document.getElementById()는 노드의 id 어트리뷰트 값으로 요소 노드를 찾아 반환하는 메서드입니다. 해당 id 값을 갖는 하나의 노드만 반환하며 id 값은 HTML 내에서 하나만 존재해야 합니다. 동일한 id를 갖는 여러 개의 노드가 존재하더라도 에러는 발생하지 않지만 이 메서드는 첫 번째 노드만 반환합니다.

  2. Document.getElementsByTagName() / Element.getElementsByTagName()

    인수로 전달된 태그 요소 노드를 반환하는 메서드입니다. 해당 HTML 내에 여러 개의 노드 객체가 존재할 경우 모든 탐색 결과를 반환하며 HTMLCollection 객체 형태로 반환됩니다. HTMLCollection 객체는 유사 배열 객체이면서 이터러블입니다.

    Document에 정의된 메서드는 DOM 전체에서 탐색하여 결과를 반환하며 Element에 정의된 메서드는 특정 요소 노드의 자손 노드 객체를 탐색한 결과를 반환합니다.

  3. Document.getElementsByClassName() / Element.getElementsByClassName()

    인수로 전달된 class 어트리뷰트 값을 가진 노드 객체들을 HTMLCollection 객체 형태로 반환합니다. class 값은 id 값과 달리 중복된 값을 사용할 수 있기 때문에 여러 개의 노드 객체가 존재할 수 있습니다.

    TagName을 통한 탐색과 같이 Document의 메서드는 DOM 전체, Element의 메서드 경우 특정 노드의 자손 노드에 대한 탐색 결과를 반환합니다.

  4. Document.querySelector() / Element.querySelector()

    CSS 선택자를 이용하여 노드에 접근하는 메서드로 인수로 전달된 CSS 선택자에 해당하는 노드 객체를 탐색하여 반환합니다.

    탐색한 결과가 여러 개인 경우 첫 번째 요소를 반환하며 없는 경우 null을 반환합니다. 또한, CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생합니다.

    원하는 CSS 선택자의 여러 개의 탐색 결과를 반환받기 원하는 경우 querySelectorAll()을 통해 접근할 수 있습니다. 이 메서드는 여러 개의 노드 객체를 갖는 DOM 컬렉션 객체인 NodeList 객체를 반환하며 이 객체는 유사 배열 객체이면서 이터러블입니다.

    CSS 선택자를 이용한 접근 메서드는 다른 접근 메서드보다 느리다는 점이 있지만, 조금 더 구체적이고 일관된 방식으로 접근할 수 있다는 장점이 존재합니다.

  5. Element.matches()

    CSS 선택자를 통해 해당 노드를 취득할 수 있는지 확인하는 메서드입니다.

  6. HTMLCollection / NodeList

    HTMLCollection과 NodeList는 DOM 컬렉션 객체로 여러 개의 결괏 값을 전달하기 위한 객체입니다. 두 객체는 유사 배열 객체이며 이터러블입니다. 따라서, 간편한 순회와 스프레드 문법 사용이 가능합니다. 또한, 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는 객체로 HTMLCollection은 항상 실시간 반영되며 NodeList는 경우에 따라 반영됩니다.

    실시간으로 반영되는 살아 있는 객체를 사용하는 경우 요소를 순회하며 상태를 변경하면 객체의 상태가 변화되어 예상대로 동작하지 않을 수 있기 때문에 주의하여야 합니다. 따라서, 대부분의 경우 살아 있지 않은 객체인 NodeList 객체를 사용하는 것을 권장하며, NodeList 객체도 childNodes 프로퍼티가 반환하는 경우 살아 있는 객체로 동작하므로 주의해야 합니다.

    가장 안전하게 사용하는 방법은 배열로 변환하여 사용하는 방법입니다.

2. 노드 탐색

DOM API를 통해 반환받은 노드 객체를 탐색하는 방법에 대해 알아보겠습니다. 반환받은 DOM 트리의 자식, 형제, 부모 노드에 접근해야 하는 경우가 있습니다.

Node가 제공하는 parentNode, previousSibling, firstChild, childNodes 프로퍼티와 Element가 제공하는 previousElementSibling, nextElementSibling을 통해 접근할 수 있습니다. 노드 탐색 프로퍼티는 모두 접근자 프로퍼티로 읽기 전용 프로퍼티입니다.

  1. 자식 노드 탐색
    1. Node.childNodes: 자식 노드를 탐색하여 NodeList 객체에 담아 반환. 텍스트 노드가 포함되어 있을 수 있음
    2. Element.children: 자식 노드 중 요소 노드만 탐색하여 HTMLCollection 객체에 담아 반환
    3. Node.firstChild: 첫 번째 자식 노드만 반환하며 텍스트 노드일 수 있음
    4. Node.lastChild: 마지막 자식 노드만 반환하며 텍스트 노드일 수 있음
    5. Element.firstElementChild: 첫 번째 자식 요소 노드 반환
    6. Element.lastElementChild: 마지막 자식 요소 노드 반환
  2. 자식 노드 존재 확인
    1. Node.hasChildNodes: 자식 노드가 존재하면 true, 존재하지 않으면 false, 텍스트 노드를 포함
    2. Element.childElementCount / Element.children.length: 텍스트 노드 제외한 자식 노드 존재 확인
  3. 부모 노드 탐색
    1. Node.parentNode: 부모 노드 탐색
  4. 형제 노드 탐색
    1. Node.previousSibling: 부모 노드가 같은 형제 노드 중 자신의 이전 노드를 반환. 텍스트 노드 포함
    2. Node.nextSibling: 부모 노드가 같은 형제 노드 중 자신의 이후 노드를 반환. 텍스트 노드 포함
    3. Element.previousElementSibling: 부모 노드가 같은 형제 노드 중 자신의 이전 노드 반환. 요소 노드만 반환
    4. Element.nextElementSibling: 부모 노드가 같은 형제 노드 중 자신의 이후 노드 반환. 요소 노드만 반환

3. 노드 정보 취득

Node.nodeType

노드 객체의 타입을 나타내는 상수를 반환합니다. Node.ELEMENT_NODE는 요소 노드 타입 1을 반환하며 Node.TEXT_NODE는 텍스트 노드 타입 3을 반환, Node.DOCUMENT_NODE는 문서 노드 타입 9를 반환합니다.

Node.nodeName

노드의 이름을 문자열로 반환합니다. 요소 노드는 대문자 태그 이름을 반환하며, 텍스트 노드는 ‘#text’, 문서 노드는 ‘#document’를 반환합니다.

4. 요소 노드의 텍스트 조작

Node.nodeValue

접근과 조작이 모두 가능한 프로퍼티로 노드 객체의 값을 반환합니다. 노드 객체의 값은 텍스트 노드의 텍스트입니다. 오직 텍스트만 반환하며 nodeValue의 프로퍼티에 값을 할당하면 텍스트 값이 변경됩니다.

Node.textContent

접근과 조작이 모두 가능한 프로퍼티로 요소 노드의 텍스트 노드와 모든 자손 노드의 텍스트 노드를 조작할 수 있습니다. 요소 노드의 childNodes 프로퍼티가 반환한 모드 노드들의 텍스트 노드의 값이며, 값이 모두 텍스트로 취급되어 HTML 마크업이 포함되어 있더라도 인식되지 않습니다.


5. DOM 조작

DOM 조작은 새로운 노드 추가나 기존 노드를 삭제 또는 교체하는 것을 의미합니다. DOM 조작 시 리플로우와 리페인트가 발생하여 성능에 영향을 미칩니다. 따라서 DOM 조작은 주의를 요구합니다.

innerHTML

Element.innerHTML은 요소 노드의 HTML 마크업을 취득하거나 변경합니다. innerHTML 참조 시 요소 노드의 시작 태그와 종료 태그 사이에 존재하는 HTML 마크업을 문자열로 반환합니다. 또한, innerHTML에 문자열을 할당하면 해당 요소 노드의 자식 노드들을 제거하고 할당한 문자열의 HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영됩니다.

innerHTML에 입력받은 데이터를 곧바로 할당하는 것은 크로스 사이트 스크립팅 공격(HTML 마크업 내에 자바스크립트 악성 코드를 포함시켜 파싱 과정에서 실행하는 공격)에 취약합니다.

또한 innerHTML은 자식 노드를 모두 삭제시키고 할당받은 문자열을 파싱하여 변경한다는 단점이 존재합니다. 이는 효율적이지 않습니다.

insertAdjacentHTML

Element.insertAdjactHTML(position, DOMString)은 기존 요소 제거 없이 위치 지정으로 새로운 요소를 삽입합니다. DOMString 인수로 전달한 문자열을 파싱하여 position 인수로 전달된 위치에 삽입하여 DOM에 반영합니다.

기존 요소의 영향을 주지 않고 삽입하는 요소만 파싱하여 추가하므로 효율적이고 빠르지만, 크로스 사이트 스크립팅 공격에 취약합니다.

노드 생성과 추가

Document.createElement(tagName)은 요소 노드를 생성하여 반환합니다. 매개변수 tagName에는 태그 이름을 나타내는 문자열을 인수로 전달합니다. 생성된 요소 노드는 DOM에 추가하는 별도의 추가 처리가 필요합니다.

Document.createTextNode(text)는 텍스트 노드를 생성하여 반환합니다. 인수로 전달된 text에 텍스트 노드의 값인 문자열을 전달합니다. 이 메서드 또한, 노드 생성만 담당할 뿐 요소 노드에 추가하지 않습니다.

Node.appendChild(childNode)는 childNode에 전달된 노드를 호출한 노드의 마지막 자식 노드로 추가합니다.

DocumentFragment 노드는 노드 객체의 일종으로 자식 노드들의 부모 노드로서 별도의 서브 DOM을 구성하여 기존 DOM에 추가하기 위해 사용합니다. 기존 DOM에는 영향을 미치지 않아 자식 노드를 추가한 다음, DOM에 추가하면 자신은 제거되고 자식 노드들만 DOM에 추가합니다. Document.createDocumentFragment는 비어 있는 DocumentFragment 노드를 생성하여 반환합니다. 실제 DOM의 변경은 한 번만 이루어져 성능에 효율적입니다.

Node.insertBefore(newNode, childNode)는 첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드 앞에 삽입합니다. 두 번째 인수는 반드시 호출한 노드의 자식 노드이어야 하며 아닌 경우, DOMException 에러가 발생합니다.

노드 복사

Node.cloneNode([deep: true | false])는 노드의 사본을 생성하여 반환하며 인수로 true를 전달하면 깊은 복사하여 모든 자손 노드를 포함하며 false로 전달하면 얕은 복사하여 자손 노드를 복사하지 않습니다.

노드 교체

Node.replaceChild(newChild, oldChild)는 자신을 호출한 노드의 자식 노드를 다른 노드로 교체합니다. newChild 인수로 교체할 새로운 노드를 전달하고, oldChild 인수로 교체될 노드를 전달하며 호출한 노드의 자식 노드이어야 합니다.

노드 삭제

Node.remove(child)는 인수로 전달된 노드를 DOM에서 삭제합니다. 인수는 호출한 노드의 자식 노드이어야 합니다.


6. 어트리뷰트

HTML 요소는 동작을 제어하기 위한 추가적인 정보를 제공하는 어트리뷰트(속성)를 가질 수 있습니다. HTML 파싱 시점에 어트리뷰트 노드로 변환되어 요소 노드와 연결됩니다. 어트리뷰트당 하나의 어트리뷰트 노드가 생성됩니다. 모든 어트리뷰트 노드는 요소 노드의 Element.attributes 프로퍼티로 취득할 수 있으며 읽기 전용 프로퍼티로 NamedNodeMap 객체를 반환합니다.

HTML 어트리뷰트 조작

Element.getAttribute/setAttribute 메서드를 통해 요소 노드에서 직접 어트리뷰트 값 취득과 변경이 가능합니다. 특정 어트리뷰트 존재 확인은 Element.hasAttribute(attributeName) 메서드를 통해 가능하며, 어트리뷰트 삭제는 Element.removeAttribute(attributeName) 메서드를 사용합니다.

HTML 어트리뷰트 vs DOM 프로퍼티

DOM 프로퍼티는 HTML 어트리뷰트의 초깃값을 가지고 있습니다. DOM 프로퍼니는 setter와 getter를 가지고 있는 접근자 프로퍼티로 참조와 변경이 가능합니다. HTML 어트리뷰트의 역할은 HTML 요소의 초기 상태를 지정하는 것으로 HTML 어트리뷰트 값은 HTML 요소의 초기 상태를 의미하며 이는 변하지 않습니다. DOM 프로퍼티는 요소 노드의 초기 상태와 최신 상태 중 최신 상태를 관리하는 역할을 합니다. 대부분의 HTML 어트리뷰트는 HTML 어트리뷰트 이름과 동일한 DOM 프로퍼티와 1:1로 대응합니다.

data 어트리뷰트와 dataset 프로퍼티

data 어트리뷰트와 dataset 프로퍼티를 통해 사용자 정의 어트리뷰트와 자바스크립트 사이의 데이터를 교환할 수 있습니다.

data 어트리뷰트 값은 HTMLElement.dataset 프로퍼티로 취득할 수 있으며 dataset 프로퍼티는 HTML 요소의 모든 data 어트리뷰트의 정보를 제공하는DOMStringMap 객체를 반환합니다.

7. 스타일

인라인 스타일 조작

HTMLElement.style 프로퍼티는 요소 노드의 인라인 스타일을 참조와 변경이 가능한 접근자 프로퍼티입니다. CSSStyleDeclaration 타입의 객체를 반환하며, CSSStyleDeclaration 객체는 CSS 프로퍼티에 대응하는 프로퍼티를 가지고 있어 값 할당 시 해당 CSS 프로퍼티가 인라인 스타일로 HTML 요소에 추가되거나 변경됩니다.

클래스 조작

Element.className 프로퍼티는 HTML 요소의 class 어트리뷰트 값을 취득하거나 변경하는 접근자 프로퍼티입니다. 참조 시에는 class 어트리뷰트 값을 문자열로 반환하고, 할당하는 경우 class 어트리뷰트 값을 할당한 문자열로 변경합니다. className 프로퍼티는 문자열을 반환하기 때문에 공백으로 구분된 여러 개의 클래스 반환 시 다루기 불편합니다.

Element.classList 프로퍼티는 class 어트리뷰트의 정보를 담은 DOMTokenList 객체를 반환합니다. DOMTokenList 객체는 class 어트리뷰트 정보를 나타내는 객체로 class 어트리뷰트 값을 추가, 삭제, 참조, 포함 여부, 변경, 토글 메서드를 제공합니다.

요소에 적용되어 있는 CSS 스타일 참조

style 프로퍼티는 인라인 스타일만 반환하기 때문에 HTML 요소에 적용되어 있는 CSS 스타일 참조가 필요한 경우 getComputedStyle 메서드를 사용합니다. 요소 노드에 작용되어 있는 평가된 스타일(링크 스타일, 임베딩 스타일, 인라인 스타일, 자바스크립트 적용 스타일, 상속 스타일, 기본 스타일)을 CSSStuleDeclaration 객체에 담아 반환합니다.


참고

이웅모, ⌈모던 자바스크립트 - Deep Dive⌋, 2020, 39_DOM(p.685 - 753)

LINKS

© Copyright 2021

made by React.js & gatsby