자바스크립트 실전 #1 DOM 조작 기본
자바스크립트 트랙의 마지막 — 실전 시리즈입니다. 기초/중급/고급에서 다진 도구를 묶어 바닐라 자바스크립트로 동적 웹 페이지를 만듭니다.
총 6편으로 구성됩니다.
- #1 DOM 조작 기본 ← 이번 글
- #2 이벤트 핸들링과 위임
- #3 fetch와 비동기 UI
- #4 폼 다루기 — validation, FormData
- #5 로컬 스토리지와 가벼운 상태 관리
- #6 작은 앱 빌드 — Todo 또는 라이브 검색
이번 글에서는 DOM을 만지는 기본을 다룹니다. 페이지의 엘리먼트를 찾고, 내용/속성/클래스를 바꾸고, 새 엘리먼트를 만들어 끼워넣는 단계입니다.
DOM이 무엇인가? #
**DOM (Document Object Model)**은 브라우저가 HTML을 자바스크립트가 다룰 수 있는 객체 트리로 변환한 것입니다. <h1>, <p> 같은 태그가 모두 자바스크립트 객체가 됩니다.
<body>
<h1 id="title">안녕</h1>
<p class="message">본문</p>
</body>이걸 자바스크립트가 보면:
document
└─ html
└─ body
├─ h1#title "안녕"
└─ p.message "본문"document가 진입점이고, 트리 구조로 모든 엘리먼트에 접근할 수 있습니다.
엘리먼트 찾기 — querySelector 계열
#
가장 자주 쓰는 메서드.
// CSS 선택자 — 한 개
const title = document.querySelector('#title');
const firstP = document.querySelector('p');
const greenBtn = document.querySelector('.btn.green');
// CSS 선택자 — 여러 개 (NodeList)
const allP = document.querySelectorAll('p');
const items = document.querySelectorAll('.item');CSS 선택자 그대로 쓸 수 있어 직관적입니다. 옛날에는 getElementById, getElementsByClassName, getElementsByTagName이 따로 있었는데, 모던 코드에서는 거의 querySelector만 씁니다.
querySelectorAll의 NodeList — 배열이 아님
#
const items = document.querySelectorAll('.item');
items.length; // OK — 길이 있음
items.forEach(...); // OK — forEach 가능
items.map(...); // ✗ map 없음
items.filter(...); // ✗ filter 없음
// 진짜 배열로 만들려면
const arr = [...items]; // OK — spread
const arr2 = Array.from(items); // OK
forEach는 NodeList 자체에 있지만, map/filter/reduce는 없습니다. 배열 메서드가 필요하면 spread 또는 Array.from으로 변환합니다.
부모/자식/형제 — 트리 탐색 #
const el = document.querySelector('.item');
// 부모
el.parentElement;
el.parentNode; // 거의 동일 (drift 케이스 있음)
// 자식
el.children; // HTMLCollection — 엘리먼트만
el.childNodes; // NodeList — 텍스트/주석 포함
el.firstElementChild;
el.lastElementChild;
// 형제
el.nextElementSibling;
el.previousElementSibling;
// 가장 가까운 조상 (자기 포함)
el.closest('.container');closest는 자주 씁니다 — 이벤트 위임(#2) 패턴의 핵심입니다.
내용 바꾸기 — textContent vs innerHTML
#
const el = document.querySelector('#title');
// 텍스트만 — 안전
el.textContent = '새 제목';
// HTML 파싱 — 위험할 수 있음
el.innerHTML = '<strong>새 제목</strong>';textContent는 텍스트로만 들어갑니다. 사용자 입력이나 외부 데이터를 넣어도 안전합니다.
innerHTML은 HTML로 파싱됩니다. <strong> 태그가 진짜 강조로 렌더되지만, 신뢰할 수 없는 데이터를 넣으면 XSS 공격에 취약해집니다.
const userInput = '<img src=x onerror="alert(1)">';
el.textContent = userInput; // 안전 — 그냥 문자열
el.innerHTML = userInput; // ✗ 스크립트 실행됨
기본은 textContent. HTML이 진짜 필요하면 그 부분에서만 innerHTML을 신중하게 쓰세요.
속성 다루기 #
일반 속성 #
const link = document.querySelector('a');
link.href; // 직접 속성으로 접근
link.href = 'https://...';
link.getAttribute('href'); // 동일
link.setAttribute('href', 'https://...');
link.removeAttribute('href');
link.hasAttribute('target'); // true/false
href, src, id 같은 흔한 속성은 직접 점 접근이 더 짧습니다. 잘 알려지지 않은 속성이나 동적 키는 getAttribute/setAttribute.
데이터 속성 — data-*
#
HTML에 자바스크립트용 데이터를 박을 때.
<button data-id="42" data-action="delete">삭제</button>const btn = document.querySelector('button');
btn.dataset.id; // '42' (항상 string)
btn.dataset.action; // 'delete'
btn.dataset.id = '100'; // 속성 갱신 → HTML 도 바뀜
camelCase 변환에 주의 — HTML의 data-user-name은 JS에서 dataset.userName으로 접근합니다.
클래스 다루기 — classList
#
const el = document.querySelector('.box');
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open'); // 있으면 빼고, 없으면 추가
el.classList.toggle('disabled', isDisabled); // 두 번째 인자로 강제
el.classList.contains('active'); // boolean
el.classList.replace('a', 'b');classList는 모던 자바스크립트의 표준 도구. 옛날의 el.className = 'a b c' (문자열로 직접 다루기) 보다 안전하고 가독성도 좋습니다.
스타일 다루기 #
인라인 스타일 #
const el = document.querySelector('.box');
el.style.color = 'red';
el.style.backgroundColor = '#eee'; // CSS 의 background-color 는 JS 에서 camelCase
el.style.transform = 'translateX(10px)';하지만 인라인 스타일은 남용하지 마세요. CSS 클래스를 토글하는 게 거의 항상 깔끔합니다.
el.classList.add('error');
// CSS 에서 .error { color: red; ... } 정의
CSS 변수 다루기 #
CSS 커스텀 속성(--var) 은 setProperty로 동적으로 바꿀 수 있습니다.
el.style.setProperty('--main-color', '#ff5733');
const value = getComputedStyle(el).getPropertyValue('--main-color');테마 변경, 동적 스타일링에 적합합니다.
엘리먼트 만들고 끼워넣기 #
새 엘리먼트 만들기 #
const div = document.createElement('div');
div.className = 'item';
div.textContent = '새 항목';끼워넣기 — append / prepend / before / after
#
const parent = document.querySelector('.list');
const item = document.createElement('li');
parent.append(item); // 끝에 추가
parent.prepend(item); // 앞에 추가
existingChild.before(item); // 기존 자식 앞에
existingChild.after(item); // 기존 자식 뒤에
existingChild.replaceWith(item); // 자식 교체
옛 메서드(appendChild, insertBefore)도 동작하지만 — 새 메서드들이 더 짧고 직관적이라 모던 코드에서는 이쪽을 사용합니다.
한 번에 여러 엘리먼트 #
parent.append(li1, li2, li3);
parent.append('텍스트도 가능', divEl, '\n');문자열도 함께 받아 자동으로 텍스트 노드로 변환해 줍니다.
제거하기 — remove
#
el.remove();옛 코드의 el.parentNode.removeChild(el)보다 훨씬 짧습니다.
효율 — DocumentFragment
#
여러 엘리먼트를 한꺼번에 추가할 때, 트리에 한 번 끼울 때마다 reflow가 일어나면 비싸요. **DocumentFragment**가 임시 컨테이너 역할을 해 줍니다.
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.append(li); // fragment 에 일단 모음
}
list.append(fragment); // 한 번만 진짜 트리에 끼움 — reflow 한 번
큰 차이는 100개 정도부터 보입니다. 대부분의 경우에는 의식할 필요가 없지만, 큰 리스트를 그릴 때 알아두면 좋습니다.
innerHTML + 템플릿 리터럴 — 짧지만 신중하게
#
list.innerHTML = items
.map((item) => `<li class="item">${item.name}</li>`)
.join('');이건 매우 짧지만 — ${item.name}이 사용자 입력이라면 XSS 위험이 있습니다. 신뢰 가능한 데이터에만, 또는 escape 처리를 거친 뒤에 쓰세요.
엘리먼트 정보 #
el.tagName; // 'DIV' (대문자)
el.id;
el.className; // 문자열 — 권장은 classList
el.getBoundingClientRect(); // 화면상 위치/크기
el.offsetWidth;
el.offsetHeight;
el.offsetTop;
el.offsetLeft;
el.scrollTop;
el.scrollLeft;레이아웃이나 스크롤을 다룰 때 자주 만나요.
closest의 활용
#
위 트리 탐색에서 잠깐 본 메서드. 이게 이벤트 핸들링에서 매우 자주 쓰여서 한 번 더 정리.
list.addEventListener('click', (e) => {
const item = e.target.closest('.item');
if (!item) return;
const id = item.dataset.id;
// ...
});리스트 안의 어디를 클릭하든, 가장 가까운 .item을 찾아 id를 끌어옵니다. 다음 글에서 본격적으로 다룰 이벤트 위임 패턴의 기반입니다.
마무리 #
이번 글에서 정리한 내용:
- DOM은 HTML을 객체 트리로 만든 자바스크립트의 인터페이스
querySelector/querySelectorAll가 모던 표준- NodeList는 배열이 아님 — 배열 메서드 필요하면 spread/Array.from
textContent가 안전,innerHTML은 신뢰 가능한 데이터에만dataset으로data-*속성 다루기classList.add/remove/toggle이 클래스 표준append/prepend/before/after가 모던 끼워넣기- 큰 리스트는
DocumentFragment로 한 번에 closest로 가장 가까운 조상 찾기
다음 글(#2 이벤트 핸들링과 위임)에서는 이벤트 객체, 버블링과 캡처링, 그리고 효율적인 이벤트 위임 패턴을 다룹니다.