DOM 이란?
DOM은 HTML 문서를 JavaScript가 이해할 수 있는 객체 트리 구조로 표현한 것입니다.
브라우저가 HTML을 파싱하면 DOM 트리가 만들어지고, JavaScript로 이 트리를 조작해서 웹페이지를 동적으로 변경할 수 있음.
즉, HTML은 단순한 텍스트 파일인데, 브라우저가 이걸 읽어서 JavaScript가 조작할 수 있는 "객체들의 집합"으로 바꿔놓은 것임
HTML 문서가 이렇게 되어 있으면
<html>
<head>
<title>제목</title>
</head>
<body>
<div id="container">
<h1>안녕</h1>
<p>내용</p>
</div>
</body>
</html>
DOM 트리는 이런 구조가 됨
document
└── html
├── head
│ └── title
│ └── "제목" (텍스트 노드)
└── body
└── div#container
├── h1
│ └── "안녕"
└── p
└── "내용"
1) DOM 요소 선택하기
DOM을 조작하려면 먼저 원하는 요소를 선택해야 함
<!-- 예시 HTML -->
<div id="header">헤더</div>
<p class="item">아이템 1</p>
<p class="item">아이템 2</p>
<p class="item">아이템 3</p>
<button>버튼 1</button>
<button>버튼 2</button>
// 1. getElementById - ID로 선택 (단일 요소)
const header = document.getElementById('header');
console.log(header); // <div id="header">헤더</div>
설명
- HTML에서 `id`는 문서 내에서 유일해야 함
- 따라서 항상 하나의 요소만 반환
- 해당 ID가 없으면 `null` 반환
// 2. getElementsByClassName - 클래스로 선택 (여러 요소)
const items = document.getElementsByClassName('item');
console.log(items); // HTMLCollection(3) [p.item, p.item, p.item]
console.log(items.length); // 3
console.log(items[0]); // <p class="item">아이템 1</p>
설명
- 같은 클래스를 가진 요소가 여러 개일 수 있으므로 HTMLCollection 반환
- HTMLCollection은 배열처럼 생겼지만 진짜 배열은 아님 (유사 배열)
- 인덱스로 접근 가능 (`items[0]`, `items[1]` 등)
- `map`, `filter` 같은 배열 메서드 직접 사용 불가
// 3. querySelector - CSS 선택자로 첫 번째 요소 선택 (가장 많이 씀!)
const firstItem = document.querySelector('.item'); // 클래스 선택
const header2 = document.querySelector('#header'); // ID 선택
const firstButton = document.querySelector('button'); // 태그 선택
const complex = document.querySelector('div.container > p'); // 복잡한 선택자
설명
- CSS에서 쓰는 선택자를 그대로 사용할 수 있음
- `.클래스`, `#아이디`, `태그`, `부모 > 자식` 등 모든 CSS 선택자 사용 가능
- 여러 요소가 매칭되어도 첫 번째 것만 반환
- 실무에서 가장 많이 사용하는 방법
// 4. querySelectorAll - CSS 선택자로 모든 요소 선택
const allItems = document.querySelectorAll('.item');
console.log(allItems); // NodeList(3) [p.item, p.item, p.item]
// NodeList는 forEach 사용 가능!
allItems.forEach(item => {
console.log(item.textContent);
});
// 아이템 1
// 아이템 2
// 아이템 3
설명
- 매칭되는 모든 요소를 NodeList로 반환
- NodeList는 `forEach` 사용 가능 (HTMLCollection과의 차이점)
- `map`, `filter` 등은 바로 사용 불가, 배열로 변환 필요: `Array.from(allItems)`
querySelector vs getElementById
- `getElementById`가 약간 더 빠름
- 하지만 `querySelector`가 더 유연하고 일관성 있음
- 실무에서는 대부분 `querySelector` / `querySelectorAll` 사용
2) DOM 요소 조작하기
요소를 선택했으면 이제 내용, 스타일, 속성 등을 변경할 수 있음.
<!-- 예시 HTML -->
<div id="container">
<h1 id="title">안녕하세요</h1>
<p id="description">설명 텍스트입니다.</p>
<button id="myBtn">클릭</button>
<img id="myImg" src="photo.jpg" alt="사진">
</div>
텍스트 내용 변경
const title = document.getElementById('title');
// textContent: 순수 텍스트만 (HTML 태그는 그대로 텍스트로 표시됨)
title.textContent = '반갑습니다';
console.log(title); // <h1 id="title">반갑습니다</h1>
title.textContent = '<span>테스트</span>';
// 화면에 "<span>테스트</span>" 라는 텍스트가 그대로 보임
// innerHTML: HTML을 파싱해서 적용
title.innerHTML = '<span style="color: red">빨간 텍스트</span>';
// 화면에 빨간색으로 "빨간 텍스트"가 보임
설명
- `textContent`: 안전함, XSS 공격 방지 (사용자 입력 표시할 때 권장)
- `innerHTML`: HTML 태그 해석됨, 동적 HTML 삽입 시 사용
- 보안상 사용자가 입력한 값은 `textContent`로 표시하는 게 좋음
스타일 변경
const title = document.getElementById('title');
// style 속성으로 인라인 스타일 적용
title.style.color = 'red';
title.style.fontSize = '24px'; // CSS: font-size → JS: fontSize (카멜케이스)
title.style.backgroundColor = '#f0f0f0'; // CSS: background-color → JS: backgroundColor
title.style.padding = '10px';
title.style.border = '1px solid black';
설명
- CSS 속성명에서 `-`를 빼고 카멜케이스로 변환
- `font-size` → `fontSize`
- `background-color` → `backgroundColor`
- `border-radius` → `borderRadius`
- 값은 문자열로 전달 (단위 포함)
- 인라인 스타일이므로 우선순위가 높음
클래스 조작 (스타일 변경의 권장 방법)
const title = document.getElementById('title');
// 클래스 추가
title.classList.add('active');
title.classList.add('highlight', 'bold'); // 여러 개 동시에 추가 가능
// 클래스 제거
title.classList.remove('active');
// 클래스 토글 (있으면 제거, 없으면 추가)
title.classList.toggle('active'); // 없으면 추가됨
title.classList.toggle('active'); // 있으면 제거됨
// 클래스 존재 여부 확인
if (title.classList.contains('active')) {
console.log('active 클래스가 있습니다');
}
// 클래스 교체
title.classList.replace('old-class', 'new-class');
설명
- 직접 style을 변경하는 것보다 클래스를 추가/제거하는 게 더 좋은 방법
- CSS 파일에 스타일을 정의해두고, JS에서는 클래스만 조작
- 유지보수와 관심사 분리 측면에서 권장되는 패턴
/* CSS 파일 */
.active {
color: red;
font-weight: bold;
}
.hidden {
display: none;
}
// JS에서는 클래스만 조작
element.classList.add('active'); // 빨간색 + 굵게
element.classList.add('hidden'); // 숨기기
element.classList.remove('hidden'); // 다시 보이기
속성(Attribute) 조작
const img = document.getElementById('myImg');
const btn = document.getElementById('myBtn');
// 속성 값 가져오기
console.log(img.getAttribute('src')); // 'photo.jpg'
console.log(img.getAttribute('alt')); // '사진'
// 속성 값 설정하기
img.setAttribute('src', 'new-photo.jpg');
img.setAttribute('alt', '새로운 사진');
// 속성 제거하기
btn.setAttribute('disabled', 'true'); // 버튼 비활성화
btn.removeAttribute('disabled'); // 버튼 다시 활성화
// 속성 존재 여부 확인
if (img.hasAttribute('alt')) {
console.log('alt 속성이 있습니다');
}
설명
- HTML 요소의 속성(src, href, alt, disabled 등)을 읽고 쓸 수 있음
- `data-*` 커스텀 속성도 조작 가능
- `disabled`, `checked`, `selected` 같은 불리언 속성도 제어 가능
// data-* 속성 활용 (커스텀 데이터 저장)
// HTML: <div id="user" data-user-id="123" data-role="admin">
const user = document.getElementById('user');
// dataset으로 접근 (data- 접두사 제외, 카멜케이스로)
console.log(user.dataset.userId); // '123'
console.log(user.dataset.role); // 'admin'
user.dataset.status = 'active'; // data-status="active" 추가됨
3) DOM 요소 생성 및 추가/삭제
동적으로 새 요소를 만들어서 페이지에 추가하거나, 기존 요소를 삭제할 수 있음
요소 생성 및 추가
// 1. 새 요소 생성
const newDiv = document.createElement('div');
// 2. 내용 및 속성 설정
newDiv.textContent = '새로 만든 div입니다';
newDiv.classList.add('new-item');
newDiv.id = 'newElement';
newDiv.style.backgroundColor = 'yellow';
// 3. DOM에 추가
const container = document.getElementById('container');
container.appendChild(newDiv); // container의 맨 뒤에 추가
// 또는
container.prepend(newDiv); // container의 맨 앞에 추가
// 또는
container.insertBefore(newDiv, 기준요소); // 특정 요소 앞에 추가
동작 설명:
- `createElement`로 요소를 만들면 아직 DOM에 연결되지 않은 상태
- 메모리에만 존재하는 "떠 있는" 요소
- `appendChild` 등으로 DOM에 연결해야 화면에 나타남
// 실용 예시: 리스트 아이템 동적 추가
const ul = document.querySelector('ul');
function addItem(text) {
const li = document.createElement('li');
li.textContent = text;
ul.appendChild(li);
}
addItem('첫 번째 아이템');
addItem('두 번째 아이템');
addItem('세 번째 아이템');
여러 요소 효율적으로 추가 (DocumentFragment)
const ul = document.querySelector('ul');
const items = ['사과', '바나나', '오렌지', '포도', '수박'];
// 비효율적인 방법: 매번 DOM에 접근
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
ul.appendChild(li); // 매번 DOM이 다시 그려짐 (5번)
});
// 효율적인 방법: DocumentFragment 사용
const fragment = document.createDocumentFragment();
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li); // fragment에 모음 (DOM 변경 없음)
});
ul.appendChild(fragment); // 한 번에 DOM에 추가 (1번만 다시 그림)
설명:
- `DocumentFragment`는 가상의 컨테이너
- 여기에 요소들을 모았다가 한 번에 DOM에 추가하면 성능이 좋음
- DOM 조작은 비용이 크기 때문에 최소화하는 게 좋음
- React가 이런 최적화를 자동으로 해줌 (Virtual DOM)
요소 삭제
const title = document.getElementById('title');
const container = document.getElementById('container');
// 방법 1: 자기 자신 삭제 (모던 방식)
title.remove();
// 방법 2: 부모에서 자식 삭제 (구 방식, IE 지원 필요시)
container.removeChild(title);
// 모든 자식 요소 삭제
container.innerHTML = ''; // 간단하지만 이벤트 리스너도 같이 제거됨
// 또는 하나씩 제거
while (container.firstChild) {
container.removeChild(container.firstChild);
}
요소 교체
const oldElement = document.getElementById('old');
const newElement = document.createElement('div');
newElement.textContent = '새로운 요소';
// 교체
oldElement.replaceWith(newElement); // 모던 방식
// 또는
oldElement.parentNode.replaceChild(newElement, oldElement); // 구 방식
2-4) DOM 탐색
현재 요소를 기준으로 부모, 자식, 형제 요소를 찾을 수 있음
<div id="parent">
<p id="first">첫째</p>
<p id="second">둘째</p>
<p id="third">셋째</p>
</div>
const second = document.getElementById('second');
// 부모 요소
console.log(second.parentElement); // <div id="parent">...</div>
// 형제 요소
console.log(second.previousElementSibling); // <p id="first">첫째</p>
console.log(second.nextElementSibling); // <p id="third">셋째</p>
// 부모의 모든 자식 요소
const parent = document.getElementById('parent');
console.log(parent.children); // HTMLCollection(3) [p, p, p]
console.log(parent.firstElementChild); // <p id="first">첫째</p>
console.log(parent.lastElementChild); // <p id="third">셋째</p>
console.log(parent.childElementCount); // 3
설명
- `parentElement`: 바로 위 부모
- `children`: 모든 직계 자식 (HTMLCollection)
- `firstElementChild` / `lastElementChild`: 첫 번째/마지막 자식
- `previousElementSibling` / `nextElementSibling`: 이전/다음 형제
- 이런 탐색 메서드를 사용하면 DOM 구조를 따라 이동할 수 있음
// 실용 예시: 클릭한 버튼의 부모 li 삭제
document.querySelector('ul').addEventListener('click', function(e) {
if (e.target.tagName === 'BUTTON') {
const li = e.target.parentElement; // 버튼의 부모인 li
li.remove();
}
});
정리해도 이해가 안간다..