DOM 조작
1. 개요
1. 개요
DOM 조작은 웹 페이지의 문서 객체 모델(DOM)을 프로그래밍적으로 조작하여 문서의 구조, 스타일, 내용을 동적으로 변경하는 핵심적인 웹 개발 기술이다. 이는 정적인 HTML 문서를 사용자의 상호작용에 반응하고 데이터에 따라 변하는 살아있는 웹 애플리케이션으로 변환하는 기반이 된다.
주요 용도는 동적 웹 페이지 생성, 사용자 인터페이스 업데이트, 데이터 시각화, 그리고 사용자 입력에 따른 반응적 변화를 구현하는 것이다. 예를 들어, 폼 제출 후 새로운 내용을 페이지에 추가하거나, 버튼 클릭 시 메뉴를 표시하는 등의 모든 동작이 DOM 조작을 통해 이루어진다.
주요 조작 대상은 문서를 구성하는 다양한 노드이며, 그중에서도 요소 노드, 텍스트 노드, 속성 노드가 가장 빈번하게 다루어진다. 이를 조작하기 위한 주요 방법으로는 요소 선택, 요소 생성 및 삭제, 요소의 속성이나 CSS 스타일 변경, 그리고 이벤트 핸들러 부착 등이 있다.
DOM 조작은 자바스크립트를 주된 언어로 사용하며, 현대의 프론트엔드 웹 개발에서 필수 불가결한 부분이다. 효율적인 DOM 조작은 웹 애플리케이션의 반응성과 성능을 직접적으로 결정짓는 중요한 요소이다.
2. DOM의 구조와 노드
2. DOM의 구조와 노드
2.1. 노드 타입
2.1. 노드 타입
DOM은 문서를 구성하는 다양한 구성 요소를 노드라는 단위로 표현한다. 각 노드는 노드 타입을 가지며, 이 타입에 따라 노드가 담고 있는 정보와 수행할 수 있는 역할이 결정된다. 가장 일반적인 노드 타입으로는 문서의 구조를 나타내는 요소 노드, 실제 텍스트 내용을 담는 텍스트 노드, 그리고 요소의 추가 정보를 정의하는 속성 노드가 있다.
이 외에도 문서 전체를 나타내는 문서 노드, 주석을 위한 주석 노드 등 여러 타입이 존재한다. 각 노드 타입은 고유한 숫자 상수로 식별되며, 자바스크립트에서는 Node.ELEMENT_NODE나 Node.TEXT_NODE와 같은 상수를 통해 타입을 확인할 수 있다. 이러한 노드 타입의 구분은 DOM 트리를 탐색하거나 특정 유형의 노드만을 필터링할 때 중요한 기준이 된다.
2.2. 노드 트리
2.2. 노드 트리
DOM은 HTML 또는 XML 문서를 계층적인 트리 구조로 표현한다. 이 구조를 노드 트리라고 부르며, 문서의 모든 구성 요소는 서로 부모-자식 관계를 이루는 노드로 표현된다. 트리의 최상위에는 문서 전체를 나타내는 Document 노드가 위치하며, 그 아래로 HTML 요소, 텍스트, 주석 등 다양한 타입의 노드가 가지를 치며 연결된다.
노드 트리에서 각 노드는 다른 노드와의 관계를 정의하는 속성을 가진다. 예를 들어, parentNode, childNodes, firstChild, lastChild, nextSibling, previousSibling 등의 속성을 통해 특정 노드의 부모, 자식, 형제 노드를 탐색할 수 있다. 이 관계를 통해 개발자는 트리를 순회하며 원하는 노드에 접근하거나, 새로운 노드를 특정 위치에 삽입하는 등 정밀한 DOM 조작이 가능해진다.
이 트리 구조는 문서의 논리적 구성을 그대로 반영한다. 예를 들어, <div> 요소 노드 안에 <p> 요소 노드가 있고, 그 안에 텍스트 노드가 포함된 구조는 트리에서 부모-자식 관계로 명확히 표현된다. 자바스크립트와 같은 클라이언트 사이드 스크립팅 언어는 이 노드 트리에 대한 API를 제공하여, 프로그래머가 웹 페이지의 내용과 구조를 동적으로 읽고 변경할 수 있도록 한다.
노드 트리의 개념은 DOM 조작의 핵심이다. 요소를 선택하거나, 새로운 콘텐츠를 추가하거나, 이벤트 처리를 위해 특정 요소를 찾는 모든 작업은 기본적으로 이 트리 구조를 이해하고 탐색하는 과정에서 시작된다. 따라서 효율적인 프론트엔드 개발을 위해서는 노드 간의 관계와 트리 탐색 방법을 숙지하는 것이 중요하다.
3. 요소 선택과 탐색
3. 요소 선택과 탐색
3.1. 선택 메서드
3.1. 선택 메서드
선택 메서드는 문서 객체 모델 내의 특정 요소나 요소 집합을 찾아 참조하는 기능을 제공한다. 이는 모든 DOM 조작의 첫 단계로, 조작할 대상을 정확히 지정하는 데 필수적이다. 주로 document 객체의 메서드를 사용하며, 각 메서드는 선택 기준과 반환 형식에 차이가 있다.
가장 널리 사용되는 메서드는 getElementById, getElementsByClassName, getElementsByTagName이다. getElementById는 주어진 id 속성 값을 가진 단일 요소를 반환한다. getElementsByClassName과 getElementsByTagName은 각각 주어진 클래스 이름이나 태그 이름을 가진 모든 요소를 HTMLCollection이라는 유사 배열 객체로 반환한다. 이들 메서드는 비교적 오래된 방법이지만 여전히 널리 지원된다.
보다 강력하고 유연한 선택을 위해 querySelector와 querySelectorAll 메서드가 도입되었다. querySelector는 CSS 선택자 문법을 인자로 받아 문서 내에서 첫 번째로 일치하는 요소 하나를 반환한다. querySelectorAll은 동일한 선택자 문법으로 일치하는 모든 요소를 정적 NodeList 객체로 반환한다. CSS 선택자를 활용할 수 있어 id, class, 태그, 계층 구조, 속성 선택 등을 복합적으로 사용한 정교한 요소 선택이 가능해진다.
메서드 | 반환 대상 | 반환 형식 | 선택 기준 |
|---|---|---|---|
| 단일 요소 | 요소 객체 |
|
| 요소 집합 |
| |
| 요소 집합 | 태그 이름 | |
| 단일 요소 | 요소 객체 | |
| 요소 집합 |
선택된 요소 집합을 담은 HTMLCollection과 NodeList는 배열과 유사하지만 완전한 배열 메서드를 모두 제공하지는 않는다. 최신 자바스크립트에서는 Array.from() 메서드나 스프레드 연산자(...)를 사용하여 실제 배열로 변환한 후 forEach, map, filter 같은 배열 메서드를 적용하여 각 요소를 순회하고 조작하는 것이 일반적이다.
3.2. 탐색 메서드
3.2. 탐색 메서드
선택된 요소를 기준으로 문서 객체 모델 트리 내에서 다른 노드를 찾아 이동하는 것을 DOM 탐색이라고 한다. 탐색은 주로 부모, 자식, 형제 노드 간의 관계를 통해 이루어진다. 자바스크립트는 이러한 탐색을 위한 다양한 속성과 메서드를 제공한다.
주요 탐색 속성으로는 parentNode, firstChild, lastChild, nextSibling, previousSibling 등이 있다. 이 속성들은 모든 타입의 노드를 대상으로 하며, 텍스트 노드나 주석 노드도 포함하여 반환한다. 예를 들어, firstChild는 요소의 첫 번째 자식 노드를 반환하는데, 이 노드가 공백 텍스트일 수도 있다.
요소 노드에 특화된 탐색 속성도 존재한다. parentElement, children, firstElementChild, lastElementChild, nextElementSibling, previousElementSibling 등이 이에 해당한다. 이 속성들은 오직 요소 노드만을 대상으로 하여, 텍스트나 주석 노드를 무시하고 원하는 요소만을 직접 선택할 수 있어 편리하다. children 속성은 자식 요소들의 컬렉션을 반환한다.
특정 요소 내에서 CSS 선택자를 사용해 하위 요소를 탐색하려면 querySelector()나 querySelectorAll() 메서드를 사용한다. 이 메서드들은 선택된 요소를 기준으로 탐색 범위를 제한할 수 있으며, 복잡한 선택자 패턴을 활용한 정밀한 탐색이 가능하다는 장점이 있다.
4. 요소 생성, 추가, 삭제
4. 요소 생성, 추가, 삭제
4.1. 생성과 추가
4.1. 생성과 추가
DOM에서 새로운 요소를 생성하려면 document.createElement() 메서드를 사용한다. 이 메서드는 지정한 태그 이름의 HTML 요소를 생성하여 요소 노드 객체로 반환한다. 생성된 노드는 아직 메모리에만 존재하며, 별도의 메서드를 사용해 문서의 특정 위치에 추가해야 실제로 화면에 나타난다.
생성된 요소를 문서 트리에 추가하는 주요 메서드로는 appendChild()와 insertBefore()가 있다. appendChild() 메서드는 특정 부모 노드의 마지막 자식 노드로 새 요소를 추가한다. 반면 insertBefore() 메서드는 부모 노드 내에서 지정된 기준 자식 노드의 바로 앞 위치에 새 요소를 삽입할 수 있어 더 정교한 위치 제어가 가능하다. 또한 innerHTML 프로퍼티를 사용해 기존 요소의 내부 HTML 마크업 문자열을 직접 교체하는 방식으로도 요소를 추가할 수 있다.
여러 요소를 한 번에 추가하거나, 텍스트와 요소를 함께 삽입해야 할 때는 append()와 prepend() 메서드가 유용하다. 이 메서드들은 DOM 레벨 1의 appendChild()에 비해 최신 웹 표준으로, 여러 개의 노드나 문자열을 인자로 받아 한 번에 처리할 수 있다. 특히 prepend()는 부모 노드의 첫 번째 자식 위치에 내용을 추가한다.
요소를 생성하고 내용을 설정한 후 최종적으로 문서에 추가하는 것이 일반적인 패턴이다. 이 과정에서 createTextNode()로 텍스트 노드를 만들거나, textContent 프로퍼티를 통해 요소의 텍스트 내용을 설정할 수 있다. 이러한 조작을 통해 개발자는 자바스크립트 코드만으로 완전히 새로운 사용자 인터페이스 컴포넌트를 동적으로 구성할 수 있다.
4.2. 삭제와 교체
4.2. 삭제와 교체
요소를 삭제하거나 교체하는 작업은 DOM의 구조를 동적으로 변경하는 핵심적인 조작이다. 부모 요소로부터 특정 자식 요소를 완전히 제거하거나, 기존 요소를 새로운 요소로 대체하는 기능을 제공한다.
요소를 삭제하는 가장 일반적인 방법은 removeChild() 메서드를 사용하는 것이다. 이 메서드는 부모 노드에서 호출하며, 제거하려는 자식 노드를 인자로 받아 DOM 트리에서 해당 노드를 분리한다. 또한, 최신 자바스크립트에서는 요소 노드 자체에서 직접 호출할 수 있는 remove() 메서드도 제공되어 더 간결한 코드 작성이 가능하다. 요소를 교체할 때는 replaceChild() 메서드를 사용한다. 이 메서드는 부모 노드에서 호출하며, 첫 번째 인자로 새 노드를, 두 번째 인자로 교체될 기존 자식 노드를 전달한다.
이러한 삭제 및 교체 작업은 사용자 인터페이스에서 목록 아이템을 제거하거나, 폼 입력 필드를 동적으로 변경하거나, 데이터 시각화 차트의 구성 요소를 업데이트하는 등 다양한 상황에서 활용된다. 작업 후에는 삭제된 노드에 대한 참조가 메모리에 남아 있을 수 있으므로, 필요하지 않은 참조를 명시적으로 null로 설정하는 것이 메모리 관리에 도움이 될 수 있다.
5. 속성과 스타일 조작
5. 속성과 스타일 조작
5.1. 속성 조작
5.1. 속성 조작
속성 조작은 문서 객체 모델 내 요소 노드에 부착된 추가 정보를 읽거나 쓰는 과정이다. HTML 요소는 id, class, src, href와 같은 다양한 속성을 가질 수 있으며, 자바스크립트를 통해 이러한 속성의 값을 동적으로 변경할 수 있다.
가장 기본적인 속성 조작 메서드는 getAttribute()와 setAttribute()이다. getAttribute() 메서드는 인자로 전달된 속성 이름의 현재 값을 문자열로 반환한다. 반면 setAttribute() 메서드는 두 개의 인자를 받아 첫 번째 인자로 지정한 속성의 값을 두 번째 인자의 값으로 설정한다. 이를 통해 이미지의 소스를 변경하거나 링크의 목적지를 업데이트하는 등의 작업이 가능하다.
일부 자주 사용되는 표준 속성(HTML 속성)은 DOM 요소 객체의 프로퍼티로 직접 접근할 수도 있다. 예를 들어, element.id나 element.className을 통해 해당 속성 값을 읽거나 쓸 수 있다. 그러나 data-*와 같은 사용자 정의 속성(커스텀 데이터 속성)은 프로퍼티로 직접 접근이 불가능하며, 반드시 getAttribute()나 setAttribute() 메서드를 사용해야 한다.
속성의 존재 여부를 확인하거나 제거해야 할 때는 hasAttribute()와 removeAttribute() 메서드를 사용한다. hasAttribute()는 특정 속성이 요소에 존재하는지 불리언 값으로 알려주며, removeAttribute()는 요소에서 지정한 속성을 완전히 삭제한다. 이는 요소의 상태나 동작을 초기화할 때 유용하게 활용된다.
5.2. 스타일 조작
5.2. 스타일 조작
DOM 요소의 시각적 표현을 변경하는 것을 스타일 조작이라고 한다. 이는 주로 요소 노드의 style 속성을 통해 이루어지며, 인라인 스타일을 직접 추가하거나 수정하는 방식이다. 예를 들어, element.style.color = 'red';와 같은 코드로 특정 요소의 글자 색상을 변경할 수 있다. 이 방법은 빠르고 직관적이지만, 우선순위가 높은 인라인 스타일이 적용되어 기존의 CSS 규칙을 덮어쓸 수 있다는 점에 유의해야 한다.
보다 구조화된 접근 방식은 요소의 classList 속성을 사용하는 것이다. classList.add(), classList.remove(), classList.toggle() 메서드를 활용하면 미리 정의된 CSS 클래스를 요소에 추가하거나 제거할 수 있다. 이 방법은 스타일 규칙을 CSS 파일에 유지하고, 자바스크립트는 단지 클래스 적용 상태만 관리하면 되므로 관심사의 분리가 명확해진다. 특히 다수의 스타일 변경이 필요하거나 상태에 따른 스타일 전환이 빈번한 경우에 효과적이다.
스타일 조작 시 계산된 스타일 값을 읽어오는 경우도 있다. element.style 속성은 인라인 스타일만 반환하므로, 최종 적용된 모든 CSS 규칙을 포함한 값을 얻기 위해서는 window.getComputedStyle(element) 메서드를 사용해야 한다. 이 메서드는 요소에 실제로 렌더링된 모든 스타일 속성의 값을 읽기 전용으로 제공하여, 현재의 시각적 상태를 정확히 파악하는 데 도움을 준다.
6. 이벤트 처리
6. 이벤트 처리
6.1. 이벤트 핸들러 등록
6.1. 이벤트 핸들러 등록
이벤트 핸들러 등록은 문서 객체 모델의 요소가 특정 이벤트에 반응하도록 코드를 연결하는 과정이다. 사용자의 클릭이나 키보드 입력, 페이지 로드 완료와 같은 사건이 발생했을 때 실행될 함수를 지정함으로써 웹 페이지를 상호작용적으로 만드는 핵심 기법이다.
주요 등록 방법에는 HTML 속성 사용, DOM 요소의 프로퍼티에 할당, 그리고 addEventListener 메서드 사용이 있다. HTML의 onclick 같은 인라인 속성은 초기 방식이지만, 자바스크립트 코드와 HTML 구조를 분리하기 위해 권장되지 않는다. element.onclick = function 형태의 프로퍼티 할당 방식은 간단하지만, 하나의 이벤트 타입에 대해 단 하나의 핸들러만 설정할 수 있다는 제약이 있다.
가장 유연하고 권장되는 방법은 addEventListener 메서드를 사용하는 것이다. 이 메서드는 같은 요소의 동일한 이벤트 타입에 대해 여러 개의 핸들러 함수를 등록할 수 있으며, 이벤트의 전파 단계(캡처링 또는 버블링)를 세밀하게 제어할 수 있다. 또한 removeEventListener 메서드를 통해 등록된 핸들러를 제거할 수 있어 메모리 관리에 유리하다.
이벤트 핸들러 내부에서 this 키워드는 일반적으로 이벤트가 바인딩된 DOM 요소를 가리킨다. 그러나 화살표 함수를 핸들러로 사용할 경우 this의 문맥이 달라질 수 있으므로 주의가 필요하다. 적절한 이벤트 위임 기법을 활용하면 다수의 하위 요소에 대한 이벤트를 상위 요소 하나에서 효율적으로 처리할 수 있어 성능을 개선할 수 있다.
6.2. 이벤트 객체
6.2. 이벤트 객체
이벤트 객체는 이벤트가 발생했을 때 브라우저에 의해 자동으로 생성되는 객체이다. 이 객체는 해당 이벤트에 대한 상세한 정보를 담고 있으며, 이벤트 핸들러 함수에 인자로 전달된다. 이를 통해 개발자는 어떤 요소에서 이벤트가 발생했는지, 어떤 키가 눌렸는지, 마우스의 위치는 어디인지 등 다양한 정보를 얻어 이벤트에 대한 구체적인 처리를 구현할 수 있다.
이벤트 객체의 주요 속성과 메서드는 이벤트의 유형에 따라 다르지만, 공통적으로 사용되는 핵심 속성이 있다. target 속성은 이벤트가 실제로 발생한 요소를 가리키며, currentTarget 속성은 이벤트 핸들러가 부착된 요소를 가리킨다. type 속성은 발생한 이벤트의 종류(예: 'click', 'keydown')를 문자열로 반환한다. 마우스 이벤트의 경우 clientX, clientY로 뷰포트 기준 좌표를, 키보드 이벤트의 경우 key나 keyCode로 눌린 키의 정보를 확인할 수 있다.
이벤트 객체의 메서드 중 가장 중요한 것은 preventDefault()와 stopPropagation()이다. preventDefault() 메서드는 해당 요소의 기본 동작(예: 하이퍼링크 클릭 시 페이지 이동, 폼 제출)을 취소한다. stopPropagation() 메서드는 이벤트의 전파를 중단시켜, 상위 요소로의 이벤트 버블링 또는 하위 요소로의 이벤트 캡처링을 방지한다. 이를 통해 이벤트의 영향을 정밀하게 제어할 수 있다.
이벤트 객체는 자바스크립트의 표준 이벤트 모델을 따르며, 최신 브라우저에서는 대부분 호환된다. 그러나 오래된 인터넷 익스플로러와의 호환성을 위해 window.event 같은 비표준 방식에 대한 폴백 처리가 필요했던 시기도 있었다. 현대의 웹 개발에서는 이러한 객체를 활용하여 사용자와의 풍부한 상호작용을 구현하는 것이 DOM 조작의 핵심 요소 중 하나이다.
7. 성능 고려사항
7. 성능 고려사항
빈번한 DOM 조작은 웹 페이지의 성능에 직접적인 영향을 미칠 수 있다. 특히 대규모 문서나 복잡한 사용자 인터페이스를 다룰 때는 성능 저하가 두드러진다. 주요 문제점으로는 리플로우와 리페인트 과정이 있다. 리플로우는 요소의 기하학적 속성(너비, 높이, 위치 등)이 변경될 때 렌더 트리를 다시 계산하고 레이아웃을 재배치하는 과정이다. 리페인트는 요소의 시각적 스타일(색상, 배경 등)이 변경되어 화면을 다시 그리는 과정이다. 이 두 과정은 브라우저의 메인 스레드에서 실행되며, 과도하게 발생하면 사용자 경험을 저해하는 지연이나 버벅임 현상을 초래한다.
성능 최적화를 위한 일반적인 전략은 DOM 접근 횟수를 최소화하고, 가능한 경우 여러 변경 사항을 일괄 처리하는 것이다. 예를 들어, 루프 내에서 스타일을 개별적으로 변경하기보다는 CSS 클래스를 미리 정의해 두고 요소의 className이나 classList를 한 번만 변경하는 것이 효율적이다. 또한, 가상 DOM을 활용하는 리액트나 뷰와 같은 프론트엔드 라이브러리는 변경 사항을 메모리 내에서 계산한 후 필요한 최소한의 실제 DOM 조작만 수행함으로써 성능을 크게 향상시킨다.
DOM 조작의 성능을 측정하고 분석하는 데는 브라우저의 개발자 도구가 필수적이다. 크롬 개발자 도구의 Performance 패널이나 렌더링 패널을 사용하면 리플로우와 리페인트가 발생하는 시점과 빈도를 시각적으로 확인할 수 있다. 또한, 자바스크립트 프로파일링을 통해 특정 함수의 실행 시간을 분석하여 병목 현상을 일으키는 코드를 찾아낼 수 있다. 이러한 도구들을 활용하여 성능 문제를 체계적으로 진단하고 최적화할 수 있다.
