🧩 JavaScript 심화 — DOM & API 완전 정복
📋 5단계 학습 개요 | DOM 조작 + fetch API | 예상 학습 기간: 3~4주
이전 단계에서 JavaScript 문법을 익혔다면, 이번엔 그 지식을 실제 웹 페이지를 움직이는 힘으로 바꿔봅니다.
DOM으로 HTML을 자유롭게 조작하고, fetch API로 서버와 통신하는 법을 배웁니다.
이 단계를 마치면 화면이 살아 움직이는 인터랙티브 웹을 만들 수 있게 됩니다!
🌳 1. DOM이란? — HTML 문서를 JS로 다루는 열쇠
DOM(Document Object Model)은 HTML 문서를 나무(Tree) 구조로 표현한 것입니다.
브라우저가 HTML 파일을 읽을 때, 모든 태그를 '노드(Node)' 객체로 변환하고 계층 구조를 만들어요.
JavaScript는 이 트리를 탐색·수정·삭제·추가할 수 있는 API를 제공합니다.
📌 비유: DOM은 HTML의 가계도(족보)입니다. 부모-자식 관계가 명확하고, JS는 그 가계도를 마음대로 수정하는 편집자 역할을 합니다.
🔍 2. DOM 요소 선택 — 원하는 요소 찾아내기
JavaScript로 HTML 요소를 조작하려면 먼저 해당 요소를 선택해야 합니다. querySelector가 현업에서 가장 많이 사용됩니다.
🔑 현업 선택 가이드:
• querySelector / querySelectorAll → 대부분의 상황에서 사용 (CSS 선택자 그대로 사용 가능)
• getElementById → 성능이 중요할 때 (가장 빠름)
• closest() → 이벤트 위임(delegation)에서 매우 유용
💻 실전 코드 예시:
// CSS 선택자를 그대로 사용
const title = document.querySelector('h1'); // 첫 번째 h1
const allCards = document.querySelectorAll('.card'); // 모든 .card
const btn = document.querySelector('#submit-btn'); // id로
const activeItem = document.querySelector('ul li.active'); // 복합 선택자
// NodeList 순회
allCards.forEach(card => {
card.style.border = '1px solid #eee';
});
// 요소가 없을 수도 있으므로 체크
const maybeNull = document.querySelector('.not-exist');
if (maybeNull) {
maybeNull.textContent = '찾았다!';
}
⚙️ 3. DOM 조작 — 요소를 변경·추가·삭제하기
요소를 선택했으면 이제 자유롭게 조작할 수 있습니다. 텍스트 변경, 스타일 변경, 클래스 제어, 새 요소 생성 등이 가능합니다.
📌 textContent vs innerHTML
• textContent — 순수 텍스트만 변경 (XSS 안전 ✅)
• innerHTML — HTML 태그 포함 변경 (사용자 입력값에 사용 시 XSS 위험 ⚠️)
현업에서는 외부 데이터를 표시할 때 반드시 textContent 사용 권장!
const title = document.querySelector('h1');
// ① 텍스트 / HTML 변경
title.textContent = '새로운 제목'; // 안전한 방법
title.innerHTML = '<strong>굵게</strong> 제목'; // HTML 삽입 시
// ② 스타일 직접 변경 (인라인 스타일)
title.style.color = '#e74c3c';
title.style.fontSize = '2rem';
title.style.display = 'none'; // 숨기기
// ③ 클래스 조작 (권장 방식 — CSS에서 스타일 관리)
title.classList.add('highlight'); // 추가
title.classList.remove('highlight'); // 제거
title.classList.toggle('active'); // 없으면 추가, 있으면 제거
title.classList.contains('active'); // 존재 여부 확인 → true/false
title.classList.replace('old', 'new'); // 교체
// ④ 속성 조작
const img = document.querySelector('img');
img.setAttribute('src', 'new.jpg');
img.getAttribute('alt');
img.removeAttribute('disabled');
// ⑤ 새 요소 생성 및 추가
const newCard = document.createElement('div');
newCard.classList.add('card');
newCard.textContent = '새 카드!';
document.body.appendChild(newCard); // 맨 끝에 추가
const list = document.querySelector('ul');
const newItem = document.createElement('li');
newItem.textContent = '새 항목';
list.insertBefore(newItem, list.firstChild); // 맨 앞에 추가
// ⑥ 요소 삭제
const oldEl = document.querySelector('.remove-me');
oldEl.remove(); // 현대적 방식
⚡ 4. 이벤트 — 사용자 동작에 반응하기
이벤트는 사용자가 버튼을 클릭하거나, 키보드를 누르거나, 마우스를 움직이는 등의 모든 동작을 의미합니다. addEventListener로 이벤트를 '감시'합니다.
// 기본 패턴
const btn = document.querySelector('#myBtn');
btn.addEventListener('click', function(event) {
console.log('클릭됨!');
console.log(event.target); // 클릭된 실제 요소
console.log(event.currentTarget); // 리스너가 달린 요소
});
// 화살표 함수 사용
btn.addEventListener('click', (e) => {
e.preventDefault(); // 기본 동작 방지 (링크 이동, 폼 제출 등)
e.stopPropagation(); // 이벤트 버블링 중단
});
// 키보드 이벤트
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter') console.log('엔터 눌림!');
if (e.key === 'Escape') console.log('ESC 눌림!');
if (e.ctrlKey && e.key === 's') {
e.preventDefault(); // Ctrl+S 기본동작 막기
saveWork();
}
});
// 폼 제출 처리 (현업 핵심 패턴)
const form = document.querySelector('#myForm');
form.addEventListener('submit', async (e) => {
e.preventDefault(); // 페이지 새로고침 방지!
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data); // 폼 데이터를 객체로
// await fetch로 서버에 전송...
});
🫧 이벤트 버블링 & 이벤트 위임
🚀 이벤트 위임(Event Delegation) — 현업 필수 패턴!
동적으로 추가되는 요소들에 이벤트를 일일이 붙이지 않고, 부모 요소 하나에만 리스너를 달고 자식 이벤트를 처리합니다.
예: 리스트 항목 100개 → 항목마다 리스너 ❌ → 부모 ul에 하나만 ✅
// ❌ 비효율적인 방법 (각 버튼마다 리스너)
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', () => { /* 삭제 처리 */ });
});
// ✅ 이벤트 위임 (부모에 하나만)
document.querySelector('#list').addEventListener('click', (e) => {
if (e.target.matches('.delete-btn')) {
e.target.closest('.item').remove();
}
if (e.target.matches('.edit-btn')) {
// 수정 처리
}
});
📚 5. 배열 & 객체 고급 메서드 — 데이터 가공의 핵심
현업에서는 서버에서 받은 배열 데이터를 map → filter → reduce 조합으로 가공하는 패턴을 매우 자주 사용합니다.
// 실전 예시: 상품 목록 가공
const products = [
{ id: 1, name: '노트북', price: 1200000, category: 'electronics', inStock: true },
{ id: 2, name: '키보드', price: 80000, category: 'electronics', inStock: false },
{ id: 3, name: '마우스', price: 30000, category: 'electronics', inStock: true },
{ id: 4, name: '책상', price: 300000, category: 'furniture', inStock: true },
];
// map — 이름만 추출
const names = products.map(p => p.name);
// ['노트북', '키보드', '마우스', '책상']
// filter — 재고 있는 것만
const available = products.filter(p => p.inStock);
// map + filter 체이닝 (현업 핵심!)
const affordableNames = products
.filter(p => p.inStock && p.price < 200000)
.map(p => p.name);
// ['마우스', '책상']
// reduce — 총 금액 계산
const totalPrice = products.reduce((sum, p) => sum + p.price, 0);
// 1610000
// find — 특정 상품 찾기
const laptop = products.find(p => p.name === '노트북');
// sort — 가격순 정렬 (원본 변경 주의!)
const sorted = [...products].sort((a, b) => a.price - b.price);
// 객체 고급 활용
const { name, price, ...rest } = products[0]; // 구조 분해
const merged = { ...products[0], price: 999000 }; // 스프레드로 업데이트
const keys = Object.keys(products[0]); // ['id', 'name', 'price', ...]
const entries = Object.entries(products[0]); // [['id', 1], ['name', '노트북'], ...]
🌐 6. fetch API — 서버와 통신하기 (AJAX)
AJAX란? — 페이지 새로고침 없이 서버에서 데이터를 가져오거나 보내는 기술
예전에는 XMLHttpRequest를 사용했지만, 현재는 fetch API(또는 axios 라이브러리)를 사용합니다.
// ① 기본 GET 요청 (데이터 가져오기)
async function fetchUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
// 응답 상태 확인
if (!response.ok) {
throw new Error('HTTP Error: ' + response.status);
}
const users = await response.json(); // JSON → JS 객체 변환
console.log(users);
} catch (error) {
console.error('요청 실패:', error.message);
}
}
// ② POST 요청 (데이터 보내기)
async function createPost(postData) {
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // JSON 전송 명시
'Authorization': 'Bearer ' + token, // 인증 토큰
},
body: JSON.stringify(postData), // JS 객체 → JSON 문자열
});
const result = await response.json();
return result;
}
// ③ 여러 요청 동시에 (Promise.all — 병렬 처리로 속도 최적화)
async function fetchAll() {
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
// 두 요청이 동시에 날아가고, 둘 다 완료되면 실행됨
}
// ④ AbortController — 요청 취소 (검색 자동완성 등)
let controller;
async function search(query) {
if (controller) controller.abort(); // 이전 요청 취소
controller = new AbortController();
const res = await fetch('/api/search?q=' + query, {
signal: controller.signal
});
return res.json();
}
🔄 HTTP 메서드 & REST API 기초
// RESTful API 패턴 (현업 표준)
GET /api/users → 목록 조회
GET /api/users/1 → 단건 조회
POST /api/users → 생성
PUT /api/users/1 → 전체 수정
PATCH /api/users/1 → 일부 수정
DELETE /api/users/1 → 삭제
// HTTP 상태 코드
200 OK → 성공
201 Created → 생성 성공
400 Bad Request → 요청 형식 오류
401 Unauthorized → 인증 필요
403 Forbidden → 권한 없음
404 Not Found → 리소스 없음
500 Server Error → 서버 내부 오류
🗄️ 7. 브라우저 스토리지 — 데이터 저장하기
// ===== localStorage 실전 패턴 =====
// 다크모드 설정 저장
function setTheme(theme) {
localStorage.setItem('theme', theme);
document.body.dataset.theme = theme;
}
const savedTheme = localStorage.getItem('theme') || 'light';
setTheme(savedTheme);
// 객체/배열 저장 (JSON 직렬화 필수)
const cartItems = [
{ id: 1, name: '상품A', qty: 2 },
{ id: 2, name: '상품B', qty: 1 },
];
localStorage.setItem('cart', JSON.stringify(cartItems));
const cart = JSON.parse(localStorage.getItem('cart')) || [];
// ===== Cookie 사용 (간단한 방식) =====
document.cookie = 'token=abc123; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/; Secure; HttpOnly';
// ===== IndexedDB (대용량 구조화 데이터) =====
// 비동기 Key-Value 저장소 — 수십 MB 가능
// 현업에서는 idb 라이브러리 또는 Dexie.js 사용 추천
🎯 8. 실전 프로젝트 — 날씨 앱 만들기
지금까지 배운 DOM 조작 + fetch API를 모두 활용한 실전 프로젝트입니다.
open-meteo.com (무료, API 키 불필요) 날씨 API를 사용합니다.
// index.html
// <button id="getWeather">날씨 확인</button>
// <div id="weatherResult"></div>
const btn = document.querySelector('#getWeather');
const resultDiv = document.querySelector('#weatherResult');
btn.addEventListener('click', async () => {
btn.textContent = '로딩 중...';
btn.disabled = true;
resultDiv.innerHTML = '';
try {
// 1. 날씨 API 요청 (서울 기준)
const url = 'https://api.open-meteo.com/v1/forecast'
+ '?latitude=37.56&longitude=126.97'
+ '¤t_weather=true'
+ '&hourly=temperature_2m,precipitation_probability';
const response = await fetch(url);
if (!response.ok) throw new Error('날씨 데이터를 불러올 수 없습니다.');
const data = await response.json();
const weather = data.current_weather;
// 2. DOM 업데이트
const card = document.createElement('div');
card.classList.add('weather-card');
card.innerHTML = [
'<h2>🌤️ 서울 현재 날씨</h2>',
'<p>🌡️ 온도: ' + weather.temperature + '°C</p>',
'<p>💨 풍속: ' + weather.windspeed + ' km/h</p>',
'<p>🕐 시각: ' + new Date(weather.time).toLocaleString('ko-KR') + '</p>',
].join('');
resultDiv.appendChild(card);
// 3. 오늘 시간별 기온 (처음 8개 = 24시간)
const hourlyTemps = data.hourly.temperature_2m.slice(0, 8);
const hourlyList = document.createElement('ul');
hourlyTemps.forEach((temp, i) => {
const li = document.createElement('li');
li.textContent = (i * 3) + '시: ' + temp + '°C';
hourlyList.appendChild(li);
});
resultDiv.appendChild(hourlyList);
} catch (error) {
resultDiv.textContent = '오류: ' + error.message;
} finally {
btn.textContent = '날씨 확인';
btn.disabled = false;
}
});
🔥 9. 현업 필수 고급 기법들
① Intersection Observer — 무한 스크롤 & 스크롤 애니메이션
// 요소가 뷰포트에 들어올 때 감지 (스크롤 이벤트보다 성능 우수!)
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible'); // 애니메이션 트리거
observer.unobserve(entry.target); // 한 번만 실행 시
}
});
}, { threshold: 0.2 }); // 20% 보일 때 실행
document.querySelectorAll('.fade-in').forEach(el => observer.observe(el));
// 무한 스크롤 패턴
const loadMoreObserver = new IntersectionObserver(async (entries) => {
if (entries[0].isIntersecting) {
const newItems = await fetchMoreItems();
renderItems(newItems);
}
});
loadMoreObserver.observe(document.querySelector('#load-trigger'));
② MutationObserver — DOM 변화 감지
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('자식 요소 추가/삭제:', mutation.addedNodes);
}
if (mutation.type === 'attributes') {
console.log('속성 변경:', mutation.attributeName);
}
});
});
observer.observe(document.body, {
childList: true, // 자식 추가/삭제 감지
subtree: true, // 하위 모든 노드 감지
attributes: true, // 속성 변경 감지
});
③ debounce & throttle — 이벤트 성능 최적화
// debounce: 마지막 호출 후 N ms 뒤에 실행 (검색 자동완성)
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce(async (e) => {
const results = await fetch('/api/search?q=' + e.target.value);
// ...
}, 300)); // 300ms 동안 입력이 없을 때 실행
// throttle: N ms에 한 번만 실행 (스크롤 이벤트)
function throttle(fn, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
fn.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
window.addEventListener('scroll', throttle(() => {
console.log('스크롤 위치:', window.scrollY);
}, 100));
④ 클립보드 & 다이얼로그 API
// 클립보드 복사 (현대적 방법)
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
showToast('복사되었습니다!');
} catch (err) {
console.error('복사 실패:', err);
}
}
// 브라우저 다이얼로그
const ok = confirm('정말 삭제하시겠습니까?');
const name = prompt('이름을 입력하세요:', '홍길동');
// 현대적 대화상자: <dialog> 요소
const modal = document.querySelector('#myModal');
modal.showModal(); // 모달로 열기
modal.close(); // 닫기
📖 10. 자주 쓰는 Web API 모음
// ① URL & 쿼리스트링 다루기
const url = new URL('https://example.com/search?q=js&page=2');
url.searchParams.get('q'); // 'js'
url.searchParams.set('page', '3');
url.searchParams.append('filter', 'new');
// ② Geolocation (위치 정보)
navigator.geolocation.getCurrentPosition(
(pos) => console.log(pos.coords.latitude, pos.coords.longitude),
(err) => console.error('위치 접근 거부')
);
// ③ Notification API (브라우저 알림)
async function notify() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
new Notification('알림 제목', { body: '알림 내용', icon: '/logo.png' });
}
}
// ④ history API (SPA 라우팅 핵심)
history.pushState({ page: 2 }, '', '/page/2'); // URL 변경 (새로고침 없이)
window.addEventListener('popstate', (e) => {
console.log('뒤로가기/앞으로가기:', e.state);
});
// ⑤ Web Workers (백그라운드 스레드)
const worker = new Worker('worker.js'); // 무거운 계산을 별도 스레드에서
worker.postMessage({ data: bigData });
worker.onmessage = (e) => console.log('계산 결과:', e.data);
// ⑥ ResizeObserver (요소 크기 변화 감지)
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
console.log('크기 변경:', entry.contentRect.width);
}
});
resizeObserver.observe(document.querySelector('.resizable'));
✅ 5단계 학습 체크리스트
✅ 기초 (반드시 마스터)
☐ querySelector / querySelectorAll로 요소 선택
☐ textContent, classList로 DOM 조작
☐ addEventListener로 클릭·폼 이벤트 처리
☐ fetch + async/await로 GET 요청
☐ map / filter / reduce 사용
🚀 심화 (현업 수준)
☐ 이벤트 위임 패턴 구현
☐ POST 요청으로 데이터 전송
☐ Promise.all 병렬 처리
☐ debounce로 검색 최적화
☐ localStorage로 상태 유지
☐ Intersection Observer로 스크롤 애니메이션
🏆 실전 미션
☐ 날씨 앱 (fetch + DOM 조작)
☐ Todo 앱 (이벤트 위임 + localStorage)
☐ 영화 검색 앱 (TMDB API 활용)
🎯 다음 단계는?
DOM API를 이제 자유롭게 다룰 수 있게 됐다면, 다음 단계는 React입니다.
React는 바닐라 JS로 하던 DOM 조작을 훨씬 체계적이고 효율적으로 관리해줍니다.
→ FrontendDevGuide0006 — React 입문으로 계속 도전하세요! 🚀