
렌더링 흐름 · 노드 선택 · 어트리뷰트 · 스타일 조작
파싱(parsing)
렌더링(rendering)
브라우저 렌더링 엔진은 HTML을 파싱해 DOM 객체 트리를 만든다
DOM은
자바스크립트에서 DOM API를 사용하면
이미 만들어진 화면을 동적으로 조작할 수 있다
HTML 요소는 파싱 과정에서 다음과 같이 객체로 변환된다.
예를 들어,
<p id="msg">hello</p>
<p> → 요소 노드id="msg" → 어트리뷰트 노드hello → 텍스트 노드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 반환CSS 선택자로 여러 요소를 한 번에 선택하는 메서드
document.querySelectorAll(selector)element.querySelectorAll(selector)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
모든 어트리뷰트 노드에 한 번에 접근하고 싶을 때 사용
<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
.value 로 실제 값에 접근어트리뷰트를 더 직관적으로 다루는 메서드들
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
동일한 정보를 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)커스텀 데이터를 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 로 자동 매핑
특징 정리
data-이름 (케밥 케이스)element.dataset.이름 (카멜 케이스)dataset 은 DOMStringMap 객체 반환<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 은 인라인 스타일만 다룬다.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
// 클래스 추가
$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 등으로querySelector / querySelectorAll 로,getAttribute / setAttribute 와 프로퍼티를 구분해서 다루고,data-* + dataset 으로,style 보다는 classList 로 관리하는 쪽이 유지보수에 유리하다