๐งฉ 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 ์ ๋ฌธ์ผ๋ก ๊ณ์ ๋์ ํ์ธ์! ๐