[JavaScript] DOM

배창민·2025년 11월 20일
post-thumbnail

DOM 핵심 정리

렌더링 흐름 · 노드 선택 · 어트리뷰트 · 스타일 조작


1. 브라우저 렌더링과 DOM

1-1. 파싱과 렌더링

  • 파싱(parsing)

    • HTML/CSS/JS 같은 텍스트를 읽어 토큰으로 쪼개고
    • 문법 구조에 맞게 파스 트리(parse tree) 를 만든 뒤
    • 이를 바탕으로 중간 코드(바이트코드 등)를 생성해 실행하는 과정
  • 렌더링(rendering)

    • 파싱된 HTML/CSS/JS 정보를 이용해
    • 브라우저 화면에 실제로 시각적으로 출력하는 과정

1-2. DOM(Document Object Model)

  • 브라우저 렌더링 엔진은 HTML을 파싱해 DOM 객체 트리를 만든다

  • DOM은

    • 문서 구조와 정보(태그, 텍스트 등)를 표현하고
    • 요소, 스타일 등을 변경할 수 있는 프로그래밍 인터페이스(DOM API) 를 제공한다
  • 자바스크립트에서 DOM API를 사용하면
    이미 만들어진 화면을 동적으로 조작할 수 있다


2. 노드와 DOM 트리 구조

2-1. 노드 종류

HTML 요소는 파싱 과정에서 다음과 같이 객체로 변환된다.

  • 요소 노드(Element Node)
  • 어트리뷰트 노드(Attribute Node)
  • 텍스트 노드(Text Node)

예를 들어,

<p id="msg">hello</p>
  • <p> → 요소 노드
  • id="msg" → 어트리뷰트 노드
  • hello → 텍스트 노드

2-2. 트리 구조와 부모-자식 관계

  • HTML 요소는 중첩 구조를 가지므로,
    DOM에서도 각 노드는 부모-자식 관계를 형성한다
  • 이렇게 노드들이 트리 형태로 연결된 구조를
    DOM 트리라고 부른다

3. 노드 취득: querySelector / querySelectorAll

3-1. querySelector

CSS 선택자로 하나의 요소를 선택하는 메서드

  • document.querySelector(selector)
  • element.querySelector(selector)
<div class="area">
  <p>first</p>
</div>
<div class="area">
  <p>second</p>
</div>
// DOM 전체에서 .area 첫 번째 요소
const $area = document.querySelector('.area');
$area.style.backgroundColor = 'gray';

// $area 내부에서 첫 번째 p 요소
const $first = $area.querySelector('p');
$first.style.color = 'white';

// 해당 요소가 없으면 null 반환
const $noElem = document.querySelector('.noElem');
console.log($noElem); // null

특징 정리

  • 첫 번째 하나만 반환
  • 없으면 null 반환

3-2. querySelectorAll

CSS 선택자로 여러 요소를 한 번에 선택하는 메서드

  • document.querySelectorAll(selector)
  • element.querySelectorAll(selector)
  • 반환값: NodeList (유사 배열, 이터러블, forEach 사용 가능)
<ul id="list">
  <li class="drink">커피</li>
  <li class="drink">콜라</li>
  <li class="food">김치찌개</li>
  <li class="food">된장찌개</li>
</ul>
// ul > li 전부 선택
const $lists = document.querySelectorAll('ul > li');
$lists.forEach(li => (li.style.backgroundColor = 'gray'));

// #list 하위의 .food 요소만 선택
const $foodLists = document
  .querySelector('#list')
  .querySelectorAll('.food');

$foodLists.forEach(food => (food.style.color = 'white'));

// 없으면 길이 0인 NodeList 반환
const $noElemList = document.querySelectorAll('.noElemList');
console.log($noElemList.length); // 0

4. 어트리뷰트(attribute) 조작

4-1. attributes 프로퍼티

모든 어트리뷰트 노드에 한 번에 접근하고 싶을 때 사용

<label for="username">유저명</label>
<input type="text" id="username" value="user01">
const attributes = document.querySelector('#username').attributes;

console.log(attributes); // NamedNodeMap
console.log(attributes.type.value);  // "text"
console.log(attributes.id.value);    // "username"
console.log(attributes.value.value); // "user01"
  • Element.prototype.attributes

    • 읽기 전용
    • NamedNodeMap 객체 반환
    • 각 항목의 .value 로 실제 값에 접근

4-2. getAttribute / setAttribute / hasAttribute / removeAttribute

어트리뷰트를 더 직관적으로 다루는 메서드들

const $input = document.querySelector('#username');

// 값 가져오기
const inputValue = $input.getAttribute('value'); // "user01"

// 값 변경하기
$input.setAttribute('value', 'user02');
<label for="nickname">닉네임</label>
<input type="text" id="nickname" value="JSMaster">
const $nickname = document.querySelector('#nickname');

console.log($nickname.hasAttribute('name'));  // false

if ($nickname.hasAttribute('value')) {
  $nickname.removeAttribute('value');
}

console.log($nickname.hasAttribute('value')); // false

4-3. attribute vs property

동일한 정보를 HTML 어트리뷰트DOM 프로퍼티가 각각 관리하는 경우가 있다.

  • HTML 어트리뷰트

    • 요소의 초기 상태
    • 변하지 않는 값
    • getAttribute, setAttribute 로 접근
  • DOM 프로퍼티

    • 브라우저에서 관리하는 최신 상태
    • 사용자 입력에 따라 계속 바뀐다
    • input.value, checkbox.checked 처럼 점 표기법으로 접근
<label for="username">유저명</label>
<input type="text" id="username" value="user01">

<label for="nickname">닉네임</label>
<input type="text" id="nickname" value="JSBeginner">
const $user = document.querySelector('#username');

$user.oninput = () => {
  console.log('value 프로퍼티 값', $user.value);
  console.log('value 어트리뷰트 값', $user.getAttribute('value'));
};

const $nickname = document.querySelector('#nickname');

// 프로퍼티로 최신 상태 변경
$nickname.value = 'JSMaster';

console.log('value 프로퍼티 값', $nickname.value);
console.log('value 어트리뷰트 값', $nickname.getAttribute('value'));

// id는 사용자가 변경하지 않으므로 어트리뷰트와 프로퍼티가 항상 같음
$nickname.id = 'nick';
console.log($nickname.id);
console.log($nickname.getAttribute('id'));

타입 차이에도 주의

<label for="check">확인</label>
<input type="checkbox" id="check" checked>
const $checkbox = document.querySelector('input[type=checkbox]');

// 어트리뷰트 값: 문자열
console.log($checkbox.getAttribute('checked')); // ""

// 프로퍼티 값: 불리언
console.log($checkbox.checked); // true

정리

  • getAttribute 값은 항상 문자열
  • 프로퍼티는 자료형에 맞는 타입을 사용
    (예: checked → boolean)

4-4. data-* 어트리뷰트와 dataset

커스텀 데이터를 HTML에 심어두고 JS에서 읽고 쓸 수 있는 방법

<ul class="boardlist">
  <li data-board-id="13" data-category-id="1">망원 맛집 추천 부탁드려요!</li>
  <li data-board-id="15" data-category-id="2">한강 러닝 크루 구해요!</li>
  <li data-board-id="18" data-category-id="3">요즘 재태크 어떻게 하시나요?</li>
</ul>
const boardlist = Array.from(
  document.querySelector('.boardlist').children
);

// 각 li 요소의 dataset 확인
boardlist.forEach(board => console.log(board.dataset));

// categoryId 가 '1' 인 요소 찾기
const board = boardlist.find(
  board => board.dataset.categoryId === '1'
);

// categoryId 변경
board.dataset.categoryId = '4';

// 새 데이터 추가
board.dataset.boardWriter = 'JS사랑해';
// HTML에서는 data-board-writer 로 자동 매핑

특징 정리

  • HTML: data-이름 (케밥 케이스)
  • JS: element.dataset.이름 (카멜 케이스)
  • datasetDOMStringMap 객체 반환

5. 스타일 조작

5-1. 인라인 스타일: element.style

<div style="color: white;">AREA</div>
const $area = document.querySelector('div');

// 인라인 스타일 전체 확인
console.log($area.style); // CSSStyleDeclaration

// 인라인 스타일 수정/추가
$area.style.width = '100px';
$area.style.height = '100px';
$area.style.backgroundColor = 'lightgray';
// $area.style['background-color'] = 'lightgray';

정리

  • element.style인라인 스타일만 다룬다
  • 값 설정 시 단위(px 등)를 직접 써야 한다
  • JS 프로퍼티 이름은 카멜 케이스, CSS 에서는 케밥 케이스

5-2. className vs classList

.area {
  width: 100px;
  height: 100px;
  border: 1px solid black;
}

.circle {
  border-radius: 50%;
}

.lightgray {
  background: lightgray;
}

.yellow {
  background: yellow;
}
<div class="area"></div>
const $area = document.querySelector('.area');

console.log($area.className); // "area"

// 문자열 통째로 교체
// $area.className = 'circle';

console.log($area.classList); // DOMTokenList

classList 메서드

// 클래스 추가
$area.classList.add('circle');
$area.classList.add('lightgray');

// 인덱스로 클래스 확인
console.log($area.classList.item(0)); // "area"
console.log($area.classList.item(1)); // "circle"
console.log($area.classList.item(2)); // "lightgray"

// 포함 여부 확인
console.log($area.classList.contains('circle')); // true
console.log($area.classList.contains('yellow')); // false

// 클래스 이름 변경
$area.classList.replace('lightgray', 'yellow');

// 토글: 있으면 제거, 없으면 추가
$area.classList.toggle('yellow'); // 제거
$area.classList.toggle('yellow'); // 추가

// 제거
$area.classList.remove('yellow');

비교 정리

  • className

    • 문자열 기반
    • 할당 시 기존 클래스들을 통째로 덮어쓴다
  • classList

    • add, remove, toggle, contains, replace 등으로
      개별 클래스를 안전하게 조작할 수 있다

정리 한 줄 요약

  • DOM은 HTML을 파싱해 만든 객체 트리이고, JS에서 이 트리를 조작해 화면을 동적으로 바꾼다
  • 요소 선택은 querySelector / querySelectorAll 로,
  • 속성은 getAttribute / setAttribute 와 프로퍼티를 구분해서 다루고,
  • 커스텀 데이터는 data-* + dataset 으로,
  • 스타일은 style 보다는 classList 로 관리하는 쪽이 유지보수에 유리하다
profile
개발자 희망자

0개의 댓글