JavaScript ์ด๋ฒคํŠธ์™€ DOM

JavaScript ์ด๋ฒคํŠธ์™€ DOM

๊ฐœ์š”

DOM(Document Object Model)์€ HTML ๋ฌธ์„œ๋ฅผ JavaScript๋กœ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค. ์ด๋ฒคํŠธ๋Š” ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ(ํด๋ฆญ, ์ž…๋ ฅ ๋“ฑ)์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

์„ ์ˆ˜ ์ง€์‹: 06_JS_Basics.md


๋ชฉ์ฐจ

  1. DOM ๊ธฐ์ดˆ
  2. ์š”์†Œ ์„ ํƒ
  3. ์š”์†Œ ๋‚ด์šฉ ์กฐ์ž‘
  4. ์†์„ฑ ์กฐ์ž‘
  5. ํด๋ž˜์Šค ์กฐ์ž‘
  6. ์Šคํƒ€์ผ ์กฐ์ž‘
  7. ์š”์†Œ ์ƒ์„ฑ๊ณผ ์‚ญ์ œ
  8. ์ด๋ฒคํŠธ ๊ธฐ์ดˆ
  9. ์ด๋ฒคํŠธ ์ข…๋ฅ˜
  10. ์ด๋ฒคํŠธ ์œ„์ž„
  11. ํผ ์ฒ˜๋ฆฌ

DOM ๊ธฐ์ดˆ

DOM ํŠธ๋ฆฌ ๊ตฌ์กฐ

document
โ””โ”€โ”€ html
    โ”œโ”€โ”€ head
    โ”‚   โ””โ”€โ”€ title
    โ””โ”€โ”€ body
        โ”œโ”€โ”€ header
        โ”‚   โ””โ”€โ”€ h1
        โ”œโ”€โ”€ main
        โ”‚   โ”œโ”€โ”€ p
        โ”‚   โ””โ”€โ”€ div
        โ””โ”€โ”€ footer

๋…ธ๋“œ ํƒ€์ž…

// ์š”์†Œ ๋…ธ๋“œ (Element)
document.body

// ํ…์ŠคํŠธ ๋…ธ๋“œ (Text)
document.body.firstChild

// ๋ฌธ์„œ ๋…ธ๋“œ (Document)
document

// ์ฃผ์„ ๋…ธ๋“œ (Comment)
<!-- ์ฃผ์„ -->

DOM ํƒ์ƒ‰

const element = document.querySelector('.box');

// ๋ถ€๋ชจ/์ž์‹
element.parentNode       // ๋ถ€๋ชจ ๋…ธ๋“œ
element.parentElement    // ๋ถ€๋ชจ ์š”์†Œ
element.children         // ์ž์‹ ์š”์†Œ๋“ค (HTMLCollection)
element.childNodes       // ์ž์‹ ๋…ธ๋“œ๋“ค (ํ…์ŠคํŠธ ํฌํ•จ)
element.firstChild       // ์ฒซ ๋ฒˆ์งธ ์ž์‹ ๋…ธ๋“œ
element.firstElementChild // ์ฒซ ๋ฒˆ์งธ ์ž์‹ ์š”์†Œ
element.lastChild        // ๋งˆ์ง€๋ง‰ ์ž์‹ ๋…ธ๋“œ
element.lastElementChild // ๋งˆ์ง€๋ง‰ ์ž์‹ ์š”์†Œ

// ํ˜•์ œ
element.nextSibling          // ๋‹ค์Œ ํ˜•์ œ ๋…ธ๋“œ
element.nextElementSibling   // ๋‹ค์Œ ํ˜•์ œ ์š”์†Œ
element.previousSibling      // ์ด์ „ ํ˜•์ œ ๋…ธ๋“œ
element.previousElementSibling // ์ด์ „ ํ˜•์ œ ์š”์†Œ
            parentElement
                 โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚            โ”‚            โ”‚
previousElement  element  nextElement
                 โ”‚
         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
         โ”‚       โ”‚       โ”‚
      first   children  last

์š”์†Œ ์„ ํƒ

๋‹จ์ผ ์š”์†Œ ์„ ํƒ

// CSS ์„ ํƒ์ž๋กœ ์ฒซ ๋ฒˆ์งธ ์š”์†Œ (๊ถŒ์žฅ)
document.querySelector('.class');
document.querySelector('#id');
document.querySelector('div.box');
document.querySelector('[data-id="123"]');

// ID๋กœ ์„ ํƒ
document.getElementById('myId');

๋‹ค์ค‘ ์š”์†Œ ์„ ํƒ

// CSS ์„ ํƒ์ž๋กœ ๋ชจ๋“  ์š”์†Œ (NodeList)
document.querySelectorAll('.item');
document.querySelectorAll('ul li');

// ํด๋ž˜์Šค๋กœ ์„ ํƒ (HTMLCollection - ์‹ค์‹œ๊ฐ„)
document.getElementsByClassName('item');

// ํƒœ๊ทธ๋กœ ์„ ํƒ (HTMLCollection - ์‹ค์‹œ๊ฐ„)
document.getElementsByTagName('div');

// name ์†์„ฑ์œผ๋กœ ์„ ํƒ
document.getElementsByName('username');

NodeList vs HTMLCollection

// NodeList (์ •์ )
const nodeList = document.querySelectorAll('.item');
nodeList.forEach(item => console.log(item));  // forEach ์‚ฌ์šฉ ๊ฐ€๋Šฅ

// HTMLCollection (๋™์ /์‹ค์‹œ๊ฐ„)
const htmlCollection = document.getElementsByClassName('item');
// forEach ์‚ฌ์šฉ ๋ถˆ๊ฐ€, ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜ ํ•„์š”
[...htmlCollection].forEach(item => console.log(item));
Array.from(htmlCollection).forEach(item => console.log(item));

๋ฒ”์œ„ ๋‚ด ์„ ํƒ

const container = document.querySelector('.container');

// container ๋‚ด๋ถ€์—์„œ ์„ ํƒ
const item = container.querySelector('.item');
const items = container.querySelectorAll('.item');

closest()

๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์กฐ์ƒ ์š”์†Œ ์ฐพ๊ธฐ

const button = document.querySelector('button');

// button์˜ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด .card ์กฐ์ƒ
const card = button.closest('.card');

// ์ž๊ธฐ ์ž์‹ ๋„ ํฌํ•จ
const self = button.closest('button');  // ์ž๊ธฐ ์ž์‹  ๋ฐ˜ํ™˜

matches()

์„ ํƒ์ž์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธ

const element = document.querySelector('.item');

element.matches('.item');      // true
element.matches('.active');    // false (ํด๋ž˜์Šค ์—†์œผ๋ฉด)
element.matches('div.item');   // true (div์ด๊ณ  .item์ด๋ฉด)

์š”์†Œ ๋‚ด์šฉ ์กฐ์ž‘

textContent

ํ…์ŠคํŠธ๋งŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค (HTML ํƒœ๊ทธ ๋ฌด์‹œ).

const el = document.querySelector('.box');

// ์ฝ๊ธฐ
console.log(el.textContent);

// ์“ฐ๊ธฐ (HTML ํƒœ๊ทธ๋Š” ํ…์ŠคํŠธ๋กœ ์ฒ˜๋ฆฌ)
el.textContent = '<strong>๊ตต๊ฒŒ</strong>';  // ํƒœ๊ทธ๊ฐ€ ๊ทธ๋Œ€๋กœ ํ‘œ์‹œ๋จ

innerHTML

HTML์„ ํฌํ•จํ•œ ๋‚ด์šฉ์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

const el = document.querySelector('.box');

// ์ฝ๊ธฐ
console.log(el.innerHTML);

// ์“ฐ๊ธฐ (HTML ํŒŒ์‹ฑ๋จ)
el.innerHTML = '<strong>๊ตต๊ฒŒ</strong>';  // ์‹ค์ œ๋กœ ๊ตต๊ฒŒ ํ‘œ์‹œ

// ์ถ”๊ฐ€
el.innerHTML += '<p>์ถ”๊ฐ€ ๋‚ด์šฉ</p>';

// โš ๏ธ ๋ณด์•ˆ ์ฃผ์˜: ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๊ทธ๋Œ€๋กœ ๋„ฃ์ง€ ๋ง ๊ฒƒ!
// el.innerHTML = userInput;  // XSS ์ทจ์•ฝ์ !

innerText vs textContent

// innerText: ํ™”๋ฉด์— ๋ณด์ด๋Š” ํ…์ŠคํŠธ๋งŒ (๋А๋ฆผ)
// textContent: ๋ชจ๋“  ํ…์ŠคํŠธ (๋น ๋ฆ„)

// display: none์ธ ์š”์†Œ์˜ ํ…์ŠคํŠธ
el.innerText;     // ํฌํ•จ ์•ˆ ๋จ
el.textContent;   // ํฌํ•จ๋จ

outerHTML

์š”์†Œ ์ž์ฒด๋ฅผ ํฌํ•จํ•œ HTML

const el = document.querySelector('.box');

// ์ฝ๊ธฐ: ์š”์†Œ ์ž์ฒด ํฌํ•จ
console.log(el.outerHTML);  // <div class="box">๋‚ด์šฉ</div>

// ์“ฐ๊ธฐ: ์š”์†Œ ์ž์ฒด๋ฅผ ๊ต์ฒด
el.outerHTML = '<span>์ƒˆ ์š”์†Œ</span>';

์†์„ฑ ์กฐ์ž‘

ํ‘œ์ค€ ์†์„ฑ

const link = document.querySelector('a');
const img = document.querySelector('img');
const input = document.querySelector('input');

// ์ง์ ‘ ์ ‘๊ทผ
link.href = 'https://example.com';
img.src = 'image.jpg';
img.alt = '์ด๋ฏธ์ง€ ์„ค๋ช…';
input.value = '์ž…๋ ฅ๊ฐ’';
input.disabled = true;
input.checked = true;

getAttribute / setAttribute

const el = document.querySelector('.box');

// ์ฝ๊ธฐ
el.getAttribute('class');
el.getAttribute('data-id');

// ์“ฐ๊ธฐ
el.setAttribute('class', 'box active');
el.setAttribute('data-id', '123');

// ์‚ญ์ œ
el.removeAttribute('data-id');

// ์กด์žฌ ํ™•์ธ
el.hasAttribute('data-id');

data ์†์„ฑ

<div id="user" data-user-id="123" data-user-name="ํ™๊ธธ๋™"></div>
const el = document.querySelector('#user');

// dataset์œผ๋กœ ์ ‘๊ทผ (camelCase ๋ณ€ํ™˜)
el.dataset.userId      // "123"
el.dataset.userName    // "ํ™๊ธธ๋™"

// ์ˆ˜์ •
el.dataset.userId = '456';
el.dataset.newAttr = 'value';  // data-new-attr ์ƒ์„ฑ

// ์‚ญ์ œ
delete el.dataset.userName;

ํด๋ž˜์Šค ์กฐ์ž‘

classList

const el = document.querySelector('.box');

// ์ถ”๊ฐ€
el.classList.add('active');
el.classList.add('highlight', 'visible');  // ์—ฌ๋Ÿฌ ๊ฐœ

// ์ œ๊ฑฐ
el.classList.remove('active');
el.classList.remove('highlight', 'visible');

// ํ† ๊ธ€ (์žˆ์œผ๋ฉด ์ œ๊ฑฐ, ์—†์œผ๋ฉด ์ถ”๊ฐ€)
el.classList.toggle('active');
el.classList.toggle('active', true);   // ๊ฐ•์ œ๋กœ ์ถ”๊ฐ€
el.classList.toggle('active', false);  // ๊ฐ•์ œ๋กœ ์ œ๊ฑฐ

// ๊ต์ฒด
el.classList.replace('old-class', 'new-class');

// ํ™•์ธ
el.classList.contains('active');  // true/false

// ๋ชจ๋“  ํด๋ž˜์Šค
el.classList.length;          // ํด๋ž˜์Šค ๊ฐœ์ˆ˜
el.classList.item(0);         // ์ฒซ ๋ฒˆ์งธ ํด๋ž˜์Šค
[...el.classList];            // ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜

className

const el = document.querySelector('.box');

// ์ „์ฒด ํด๋ž˜์Šค ๋ฌธ์ž์—ด
el.className;                    // "box highlight"
el.className = 'new-class';      // ์ „์ฒด ๊ต์ฒด
el.className += ' another';      // ์ถ”๊ฐ€ (๊ณต๋ฐฑ ์ฃผ์˜)

์Šคํƒ€์ผ ์กฐ์ž‘

style ์†์„ฑ

const el = document.querySelector('.box');

// ๊ฐœ๋ณ„ ์Šคํƒ€์ผ (camelCase)
el.style.backgroundColor = 'red';
el.style.fontSize = '20px';
el.style.marginTop = '10px';
el.style.display = 'none';

// CSS ์†์„ฑ๋ช… ๊ทธ๋Œ€๋กœ (๋Œ€๊ด„ํ˜ธ)
el.style['background-color'] = 'red';

// ์—ฌ๋Ÿฌ ์Šคํƒ€์ผ ํ•œ ๋ฒˆ์—
el.style.cssText = 'color: red; font-size: 20px;';

// ์Šคํƒ€์ผ ์ œ๊ฑฐ
el.style.backgroundColor = '';
el.style.removeProperty('background-color');

getComputedStyle

์‹ค์ œ ์ ์šฉ๋œ ์Šคํƒ€์ผ ์ฝ๊ธฐ

const el = document.querySelector('.box');
const styles = getComputedStyle(el);

styles.backgroundColor;  // "rgb(255, 0, 0)"
styles.fontSize;         // "16px"
styles.display;          // "block"

// ์˜์‚ฌ ์š”์†Œ ์Šคํƒ€์ผ
const beforeStyles = getComputedStyle(el, '::before');

์š”์†Œ ์ƒ์„ฑ๊ณผ ์‚ญ์ œ

์š”์†Œ ์ƒ์„ฑ

// ์š”์†Œ ์ƒ์„ฑ
const div = document.createElement('div');
div.className = 'box';
div.id = 'myBox';
div.textContent = '์ƒˆ ์š”์†Œ';

// ํ…์ŠคํŠธ ๋…ธ๋“œ ์ƒ์„ฑ
const text = document.createTextNode('ํ…์ŠคํŠธ');

// ๋ฌธ์„œ ์กฐ๊ฐ (์—ฌ๋Ÿฌ ์š”์†Œ ๋ฌถ์Œ)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const item = document.createElement('li');
    item.textContent = `ํ•ญ๋ชฉ ${i}`;
    fragment.appendChild(item);
}
list.appendChild(fragment);  // ํ•œ ๋ฒˆ๋งŒ DOM ์—…๋ฐ์ดํŠธ

์š”์†Œ ์ถ”๊ฐ€

const parent = document.querySelector('.parent');
const child = document.createElement('div');

// ๋์— ์ถ”๊ฐ€
parent.appendChild(child);
parent.append(child);            // ํ…์ŠคํŠธ๋„ ๊ฐ€๋Šฅ
parent.append(child, 'ํ…์ŠคํŠธ');  // ์—ฌ๋Ÿฌ ๊ฐœ ๊ฐ€๋Šฅ

// ์•ž์— ์ถ”๊ฐ€
parent.prepend(child);

// ํŠน์ • ์œ„์น˜์— ์‚ฝ์ž…
const reference = document.querySelector('.reference');
parent.insertBefore(child, reference);  // reference ์•ž์—

// insertAdjacentHTML
parent.insertAdjacentHTML('beforebegin', '<div>์•ž</div>');
parent.insertAdjacentHTML('afterbegin', '<div>์ฒซ ์ž์‹</div>');
parent.insertAdjacentHTML('beforeend', '<div>๋งˆ์ง€๋ง‰ ์ž์‹</div>');
parent.insertAdjacentHTML('afterend', '<div>๋’ค</div>');

// insertAdjacentElement
parent.insertAdjacentElement('beforeend', child);
<!-- beforebegin -->
<parent>
    <!-- afterbegin -->
    ๊ธฐ์กด ๋‚ด์šฉ
    <!-- beforeend -->
</parent>
<!-- afterend -->

์š”์†Œ ์‚ญ์ œ

const el = document.querySelector('.box');

// ์ž๊ธฐ ์ž์‹  ์‚ญ์ œ
el.remove();

// ๋ถ€๋ชจ์—์„œ ์ž์‹ ์‚ญ์ œ
parent.removeChild(el);

// ๋ชจ๋“  ์ž์‹ ์‚ญ์ œ
parent.innerHTML = '';
// ๋˜๋Š”
while (parent.firstChild) {
    parent.removeChild(parent.firstChild);
}
// ๋˜๋Š”
parent.replaceChildren();

์š”์†Œ ๋ณต์ œ

const el = document.querySelector('.box');

// ์–•์€ ๋ณต์ œ (์š”์†Œ๋งŒ)
const shallow = el.cloneNode(false);

// ๊นŠ์€ ๋ณต์ œ (์ž์‹ ํฌํ•จ)
const deep = el.cloneNode(true);

// ๋ฌธ์„œ์— ์ถ”๊ฐ€
document.body.appendChild(deep);

์š”์†Œ ๊ต์ฒด

const oldEl = document.querySelector('.old');
const newEl = document.createElement('div');

// ๊ต์ฒด
oldEl.replaceWith(newEl);

// ๋ถ€๋ชจ๋ฅผ ํ†ตํ•ด ๊ต์ฒด
parent.replaceChild(newEl, oldEl);

์ด๋ฒคํŠธ ๊ธฐ์ดˆ

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก

const button = document.querySelector('button');

// addEventListener (๊ถŒ์žฅ)
button.addEventListener('click', function(event) {
    console.log('ํด๋ฆญ๋จ!');
});

// ํ™”์‚ดํ‘œ ํ•จ์ˆ˜
button.addEventListener('click', (e) => {
    console.log('ํด๋ฆญ๋จ!');
});

// ํ•ธ๋“ค๋Ÿฌ ๋ถ„๋ฆฌ
function handleClick(event) {
    console.log('ํด๋ฆญ๋จ!');
}
button.addEventListener('click', handleClick);

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ œ๊ฑฐ

// ๊ฐ™์€ ํ•จ์ˆ˜ ์ฐธ์กฐ ํ•„์š”
function handleClick(event) {
    console.log('ํด๋ฆญ๋จ!');
}

button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);

// ์ต๋ช… ํ•จ์ˆ˜๋Š” ์ œ๊ฑฐ ๋ถˆ๊ฐ€
button.addEventListener('click', () => {});  // ์ œ๊ฑฐ ๋ถˆ๊ฐ€

์ด๋ฒคํŠธ ๊ฐ์ฒด

button.addEventListener('click', function(event) {
    // ์ด๋ฒคํŠธ ์ •๋ณด
    event.type;          // "click"
    event.target;        // ์‹ค์ œ ํด๋ฆญ๋œ ์š”์†Œ
    event.currentTarget; // ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋“ฑ๋ก๋œ ์š”์†Œ
    event.timeStamp;     // ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ๊ฐ„

    // ๋งˆ์šฐ์Šค ์œ„์น˜
    event.clientX;       // ๋ทฐํฌํŠธ ๊ธฐ์ค€ X
    event.clientY;       // ๋ทฐํฌํŠธ ๊ธฐ์ค€ Y
    event.pageX;         // ๋ฌธ์„œ ๊ธฐ์ค€ X
    event.pageY;         // ๋ฌธ์„œ ๊ธฐ์ค€ Y

    // ํ‚ค๋ณด๋“œ ์ •๋ณด
    event.key;           // "Enter", "a", "Escape" ๋“ฑ
    event.code;          // "Enter", "KeyA", "Escape" ๋“ฑ
    event.shiftKey;      // Shift ๋ˆŒ๋ ธ๋Š”์ง€
    event.ctrlKey;       // Ctrl ๋ˆŒ๋ ธ๋Š”์ง€
    event.altKey;        // Alt ๋ˆŒ๋ ธ๋Š”์ง€
    event.metaKey;       // Cmd(Mac)/Win ๋ˆŒ๋ ธ๋Š”์ง€
});

๊ธฐ๋ณธ ๋™์ž‘ ๋ฐฉ์ง€

// ๋งํฌ ํด๋ฆญ ์‹œ ์ด๋™ ๋ฐฉ์ง€
link.addEventListener('click', function(event) {
    event.preventDefault();
    console.log('์ด๋™ํ•˜์ง€ ์•Š์Œ');
});

// ํผ ์ œ์ถœ ๋ฐฉ์ง€
form.addEventListener('submit', function(event) {
    event.preventDefault();
    console.log('์ œ์ถœํ•˜์ง€ ์•Š์Œ');
});

์ด๋ฒคํŠธ ์ „ํŒŒ ์ค‘์ง€

// ๋ฒ„๋ธ”๋ง ์ค‘์ง€
inner.addEventListener('click', function(event) {
    event.stopPropagation();
    // ์ƒ์œ„ ์š”์†Œ๋กœ ์ด๋ฒคํŠธ ์ „ํŒŒ๋˜์ง€ ์•Š์Œ
});

// ๊ฐ™์€ ์š”์†Œ์˜ ๋‹ค๋ฅธ ํ•ธ๋“ค๋Ÿฌ๋„ ์ค‘์ง€
inner.addEventListener('click', function(event) {
    event.stopImmediatePropagation();
});

์ด๋ฒคํŠธ ์˜ต์…˜

element.addEventListener('click', handler, {
    once: true,      // ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰ ํ›„ ์ œ๊ฑฐ
    capture: true,   // ์บก์ฒ˜ ๋‹จ๊ณ„์—์„œ ์‹คํ–‰
    passive: true    // preventDefault ํ˜ธ์ถœ ์•ˆ ํ•จ (์Šคํฌ๋กค ์„ฑ๋Šฅ)
});

// ์บก์ฒ˜ ๋‹จ๊ณ„
element.addEventListener('click', handler, true);

์ด๋ฒคํŠธ ํ๋ฆ„

       ์บก์ฒ˜ ๋‹จ๊ณ„                  ๋ฒ„๋ธ”๋ง ๋‹จ๊ณ„
         (1)                        (4)
          โ†“                          โ†‘
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  document                           โ”‚
    โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
    โ”‚   โ”‚ parent                (2) (3) โ”‚ โ”‚
    โ”‚   โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚ โ”‚
    โ”‚   โ”‚   โ”‚ target         ํด๋ฆญ!  โ”‚   โ”‚ โ”‚
    โ”‚   โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚ โ”‚
    โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

์ด๋ฒคํŠธ ์ข…๋ฅ˜

๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ

// ํด๋ฆญ
element.addEventListener('click', handler);      // ํด๋ฆญ
element.addEventListener('dblclick', handler);   // ๋”๋ธ”ํด๋ฆญ
element.addEventListener('contextmenu', handler); // ์šฐํด๋ฆญ

// ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ
element.addEventListener('mousedown', handler);  // ๋ฒ„ํŠผ ๋ˆ„๋ฆ„
element.addEventListener('mouseup', handler);    // ๋ฒ„ํŠผ ๋—Œ

// ๋งˆ์šฐ์Šค ์ด๋™
element.addEventListener('mousemove', handler);  // ์ด๋™
element.addEventListener('mouseenter', handler); // ์š”์†Œ ์ง„์ž… (๋ฒ„๋ธ”๋ง X)
element.addEventListener('mouseleave', handler); // ์š”์†Œ ์ดํƒˆ (๋ฒ„๋ธ”๋ง X)
element.addEventListener('mouseover', handler);  // ์š”์†Œ ์œ„๋กœ (๋ฒ„๋ธ”๋ง O)
element.addEventListener('mouseout', handler);   // ์š”์†Œ ๋ฒ—์–ด๋‚จ (๋ฒ„๋ธ”๋ง O)

// ๋งˆ์šฐ์Šค ๋ฒ„ํŠผ ํ™•์ธ
element.addEventListener('mousedown', (e) => {
    e.button;  // 0: ์ขŒํด๋ฆญ, 1: ํœ , 2: ์šฐํด๋ฆญ
});

ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ

// ํ‚ค ์ด๋ฒคํŠธ
document.addEventListener('keydown', handler);  // ํ‚ค ๋ˆ„๋ฆ„
document.addEventListener('keyup', handler);    // ํ‚ค ๋—Œ
document.addEventListener('keypress', handler); // ๋ฌธ์ž ํ‚ค (deprecated)

// ํ‚ค ํ™•์ธ
document.addEventListener('keydown', (e) => {
    console.log(e.key);   // "a", "Enter", "Escape"
    console.log(e.code);  // "KeyA", "Enter", "Escape"

    // ํŠน์ˆ˜ ํ‚ค
    if (e.key === 'Enter') { }
    if (e.key === 'Escape') { }
    if (e.key === 'ArrowUp') { }
    if (e.key === 'ArrowDown') { }

    // ์กฐํ•ฉ ํ‚ค
    if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        console.log('์ €์žฅ');
    }
});

ํผ ์ด๋ฒคํŠธ

// ์ž…๋ ฅ
input.addEventListener('input', handler);    // ๊ฐ’ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค
input.addEventListener('change', handler);   // ํฌ์ปค์Šค ์žƒ์„ ๋•Œ (๊ฐ’ ๋ณ€๊ฒฝ ์‹œ)

// ํฌ์ปค์Šค
input.addEventListener('focus', handler);    // ํฌ์ปค์Šค ๋ฐ›์Œ
input.addEventListener('blur', handler);     // ํฌ์ปค์Šค ์žƒ์Œ
input.addEventListener('focusin', handler);  // ํฌ์ปค์Šค ๋ฐ›์Œ (๋ฒ„๋ธ”๋ง O)
input.addEventListener('focusout', handler); // ํฌ์ปค์Šค ์žƒ์Œ (๋ฒ„๋ธ”๋ง O)

// ์ œ์ถœ
form.addEventListener('submit', handler);
form.addEventListener('reset', handler);

์Šคํฌ๋กค/๋ฆฌ์‚ฌ์ด์ฆˆ ์ด๋ฒคํŠธ

// ์Šคํฌ๋กค
window.addEventListener('scroll', handler);
element.addEventListener('scroll', handler);

// ์Šคํฌ๋กค ์œ„์น˜
window.addEventListener('scroll', () => {
    console.log(window.scrollY);  // ์ˆ˜์ง ์Šคํฌ๋กค ์œ„์น˜
    console.log(window.scrollX);  // ์ˆ˜ํ‰ ์Šคํฌ๋กค ์œ„์น˜
});

// ๋ฆฌ์‚ฌ์ด์ฆˆ
window.addEventListener('resize', handler);
window.addEventListener('resize', () => {
    console.log(window.innerWidth);
    console.log(window.innerHeight);
});

// ์„ฑ๋Šฅ ์ตœ์ ํ™”: throttle/debounce ํ•„์š”

๋กœ๋“œ ์ด๋ฒคํŠธ

// ๋ฌธ์„œ ๋กœ๋“œ
window.addEventListener('load', handler);           // ๋ชจ๋“  ๋ฆฌ์†Œ์Šค ๋กœ๋“œ ํ›„
document.addEventListener('DOMContentLoaded', handler); // DOM ํŒŒ์‹ฑ ์™„๋ฃŒ ์‹œ

// ๊ถŒ์žฅ ํŒจํ„ด
document.addEventListener('DOMContentLoaded', () => {
    // DOM ์กฐ์ž‘ ์ฝ”๋“œ
});

// ๋˜๋Š” defer ์Šคํฌ๋ฆฝํŠธ ์‚ฌ์šฉ
// <script src="main.js" defer></script>

// ์ด๋ฏธ์ง€ ๋กœ๋“œ
img.addEventListener('load', handler);
img.addEventListener('error', handler);

// ํŽ˜์ด์ง€ ์ดํƒˆ
window.addEventListener('beforeunload', (e) => {
    e.preventDefault();
    e.returnValue = '';  // ํ™•์ธ ๋Œ€ํ™”์ƒ์ž ํ‘œ์‹œ
});

ํ„ฐ์น˜ ์ด๋ฒคํŠธ

element.addEventListener('touchstart', handler);  // ํ„ฐ์น˜ ์‹œ์ž‘
element.addEventListener('touchmove', handler);   // ํ„ฐ์น˜ ์ด๋™
element.addEventListener('touchend', handler);    // ํ„ฐ์น˜ ์ข…๋ฃŒ
element.addEventListener('touchcancel', handler); // ํ„ฐ์น˜ ์ทจ์†Œ

// ํ„ฐ์น˜ ์ •๋ณด
element.addEventListener('touchstart', (e) => {
    const touch = e.touches[0];
    console.log(touch.clientX, touch.clientY);
});

์ด๋ฒคํŠธ ์œ„์ž„

๊ฐœ๋…

๋ถ€๋ชจ ์š”์†Œ์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋“ฑ๋กํ•˜์—ฌ ์ž์‹ ์š”์†Œ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

<ul id="list">
    <li data-id="1">ํ•ญ๋ชฉ 1</li>
    <li data-id="2">ํ•ญ๋ชฉ 2</li>
    <li data-id="3">ํ•ญ๋ชฉ 3</li>
    <!-- ๋™์ ์œผ๋กœ ์ถ”๊ฐ€๋˜๋Š” ํ•ญ๋ชฉ๋“ค... -->
</ul>
// ๋‚˜์œ ์˜ˆ: ๊ฐ ์š”์†Œ์— ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
document.querySelectorAll('#list li').forEach(li => {
    li.addEventListener('click', handleClick);
});

// ์ข‹์€ ์˜ˆ: ๋ถ€๋ชจ์— ์ด๋ฒคํŠธ ์œ„์ž„
document.querySelector('#list').addEventListener('click', (e) => {
    // ํด๋ฆญ๋œ ์š”์†Œ๊ฐ€ li์ธ์ง€ ํ™•์ธ
    if (e.target.tagName === 'LI') {
        console.log('ํด๋ฆญ๋œ ํ•ญ๋ชฉ:', e.target.dataset.id);
    }

    // ๋˜๋Š” closest ์‚ฌ์šฉ
    const li = e.target.closest('li');
    if (li) {
        console.log('ํด๋ฆญ๋œ ํ•ญ๋ชฉ:', li.dataset.id);
    }
});

์ด๋ฒคํŠธ ์œ„์ž„์˜ ์žฅ์ 

  1. ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ: ๋ฆฌ์Šค๋„ˆ ์ˆ˜ ๊ฐ์†Œ
  2. ๋™์  ์š”์†Œ: ๋‚˜์ค‘์— ์ถ”๊ฐ€๋œ ์š”์†Œ๋„ ์ฒ˜๋ฆฌ
  3. ๊ฐ„๋‹จํ•œ ๊ด€๋ฆฌ: ํ•˜๋‚˜์˜ ๋ฆฌ์Šค๋„ˆ๋กœ ๊ด€๋ฆฌ

์‹ค์ „ ์˜ˆ์ œ

// Todo ๋ฆฌ์ŠคํŠธ
const todoList = document.querySelector('#todo-list');

todoList.addEventListener('click', (e) => {
    const target = e.target;
    const todoItem = target.closest('.todo-item');

    if (!todoItem) return;

    // ์™„๋ฃŒ ์ฒดํฌ
    if (target.matches('.checkbox')) {
        todoItem.classList.toggle('completed');
    }

    // ์‚ญ์ œ ๋ฒ„ํŠผ
    if (target.matches('.delete-btn')) {
        todoItem.remove();
    }

    // ํŽธ์ง‘ ๋ฒ„ํŠผ
    if (target.matches('.edit-btn')) {
        const text = todoItem.querySelector('.text');
        text.contentEditable = 'true';
        text.focus();
    }
});

ํผ ์ฒ˜๋ฆฌ

ํผ ์š”์†Œ ์ ‘๊ทผ

const form = document.querySelector('#myForm');

// name์œผ๋กœ ์ ‘๊ทผ
form.username;           // name="username"์ธ ์š”์†Œ
form.elements.username;  // ๊ฐ™์Œ
form.elements['user-name']; // ํ•˜์ดํ”ˆ ํฌํ•จ ์‹œ

// ๋ชจ๋“  ์š”์†Œ
form.elements;           // HTMLFormControlsCollection
form.elements.length;    // ์š”์†Œ ๊ฐœ์ˆ˜

์ž…๋ ฅ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ

// text, password, email, textarea
const textValue = input.value;

// checkbox
const isChecked = checkbox.checked;

// radio
const radioGroup = document.querySelectorAll('input[name="gender"]');
let selectedValue;
radioGroup.forEach(radio => {
    if (radio.checked) selectedValue = radio.value;
});
// ๋˜๋Š”
const selected = document.querySelector('input[name="gender"]:checked');

// select
const selectValue = select.value;
const selectedIndex = select.selectedIndex;
const selectedOption = select.options[select.selectedIndex];

// select multiple
const selectedOptions = [...select.selectedOptions].map(opt => opt.value);

// file
const files = fileInput.files;
const firstFile = files[0];

ํผ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

const form = document.querySelector('#myForm');

// ์ œ์ถœ
form.addEventListener('submit', (e) => {
    e.preventDefault();

    // FormData๋กœ ๋ชจ๋“  ๊ฐ’ ์ˆ˜์ง‘
    const formData = new FormData(form);

    // ๊ฐœ๋ณ„ ๊ฐ’
    formData.get('username');

    // ๋ชจ๋“  ๊ฐ’ ๊ฐ์ฒด๋กœ
    const data = Object.fromEntries(formData);

    // ๋˜๋Š” ์ˆœํšŒ
    for (const [key, value] of formData) {
        console.log(key, value);
    }
});

// ์ž…๋ ฅ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ฆ
input.addEventListener('input', (e) => {
    const value = e.target.value;
    if (value.length < 3) {
        e.target.classList.add('error');
    } else {
        e.target.classList.remove('error');
    }
});

// ๋ณ€๊ฒฝ ๊ฐ์ง€
input.addEventListener('change', (e) => {
    console.log('๊ฐ’ ๋ณ€๊ฒฝ:', e.target.value);
});

ํผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

const form = document.querySelector('#myForm');
const email = document.querySelector('#email');

form.addEventListener('submit', (e) => {
    // HTML5 ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    if (!form.checkValidity()) {
        e.preventDefault();
        form.reportValidity();  // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
        return;
    }

    // ๊ฐœ๋ณ„ ์š”์†Œ ๊ฒ€์‚ฌ
    if (!email.validity.valid) {
        if (email.validity.valueMissing) {
            console.log('์ด๋ฉ”์ผ ํ•„์ˆ˜');
        }
        if (email.validity.typeMismatch) {
            console.log('์ด๋ฉ”์ผ ํ˜•์‹ ์˜ค๋ฅ˜');
        }
    }
});

// ์ปค์Šคํ…€ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
email.addEventListener('invalid', (e) => {
    e.target.setCustomValidity('์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”');
});

email.addEventListener('input', (e) => {
    e.target.setCustomValidity('');  // ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ดˆ๊ธฐํ™”
});

validity ์†์„ฑ

input.validity.valid          // ์ „์ฒด ์œ ํšจ์„ฑ
input.validity.valueMissing   // required์ธ๋ฐ ๋น„์–ด์žˆ์Œ
input.validity.typeMismatch   // type ๋ถˆ์ผ์น˜ (email, url ๋“ฑ)
input.validity.patternMismatch // pattern ๋ถˆ์ผ์น˜
input.validity.tooLong        // maxlength ์ดˆ๊ณผ
input.validity.tooShort       // minlength ๋ฏธ๋‹ฌ
input.validity.rangeOverflow  // max ์ดˆ๊ณผ
input.validity.rangeUnderflow // min ๋ฏธ๋‹ฌ
input.validity.stepMismatch   // step ๋ถˆ์ผ์น˜

์‹ค์ „ ์˜ˆ์ œ

ํƒญ ๋ฉ”๋‰ด

<div class="tabs">
    <div class="tab-buttons">
        <button class="tab-btn active" data-tab="tab1">ํƒญ 1</button>
        <button class="tab-btn" data-tab="tab2">ํƒญ 2</button>
        <button class="tab-btn" data-tab="tab3">ํƒญ 3</button>
    </div>
    <div class="tab-content">
        <div class="tab-panel active" id="tab1">๋‚ด์šฉ 1</div>
        <div class="tab-panel" id="tab2">๋‚ด์šฉ 2</div>
        <div class="tab-panel" id="tab3">๋‚ด์šฉ 3</div>
    </div>
</div>
const tabButtons = document.querySelector('.tab-buttons');

tabButtons.addEventListener('click', (e) => {
    const button = e.target.closest('.tab-btn');
    if (!button) return;

    // ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
    document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
    });
    button.classList.add('active');

    // ํŒจ๋„ ํ‘œ์‹œ
    const tabId = button.dataset.tab;
    document.querySelectorAll('.tab-panel').forEach(panel => {
        panel.classList.remove('active');
    });
    document.getElementById(tabId).classList.add('active');
});

๋ชจ๋‹ฌ

<button id="openModal">๋ชจ๋‹ฌ ์—ด๊ธฐ</button>

<div class="modal" id="modal">
    <div class="modal-overlay"></div>
    <div class="modal-content">
        <button class="modal-close">&times;</button>
        <h2>๋ชจ๋‹ฌ ์ œ๋ชฉ</h2>
        <p>๋ชจ๋‹ฌ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</p>
    </div>
</div>
const modal = document.getElementById('modal');
const openBtn = document.getElementById('openModal');

// ์—ด๊ธฐ
openBtn.addEventListener('click', () => {
    modal.classList.add('open');
    document.body.style.overflow = 'hidden';
});

// ๋‹ซ๊ธฐ (์ด๋ฒคํŠธ ์œ„์ž„)
modal.addEventListener('click', (e) => {
    if (e.target.matches('.modal-close') ||
        e.target.matches('.modal-overlay')) {
        modal.classList.remove('open');
        document.body.style.overflow = '';
    }
});

// ESC ํ‚ค๋กœ ๋‹ซ๊ธฐ
document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && modal.classList.contains('open')) {
        modal.classList.remove('open');
        document.body.style.overflow = '';
    }
});

Todo ๋ฆฌ์ŠคํŠธ

<div class="todo-app">
    <form id="todo-form">
        <input type="text" id="todo-input" placeholder="ํ•  ์ผ ์ž…๋ ฅ" required>
        <button type="submit">์ถ”๊ฐ€</button>
    </form>
    <ul id="todo-list"></ul>
</div>
const form = document.getElementById('todo-form');
const input = document.getElementById('todo-input');
const list = document.getElementById('todo-list');

// ์ถ”๊ฐ€
form.addEventListener('submit', (e) => {
    e.preventDefault();

    const text = input.value.trim();
    if (!text) return;

    const li = document.createElement('li');
    li.innerHTML = `
        <input type="checkbox" class="todo-check">
        <span class="todo-text">${text}</span>
        <button class="todo-delete">์‚ญ์ œ</button>
    `;

    list.appendChild(li);
    input.value = '';
    input.focus();
});

// ์™„๋ฃŒ/์‚ญ์ œ (์ด๋ฒคํŠธ ์œ„์ž„)
list.addEventListener('click', (e) => {
    const li = e.target.closest('li');
    if (!li) return;

    if (e.target.matches('.todo-check')) {
        li.classList.toggle('completed', e.target.checked);
    }

    if (e.target.matches('.todo-delete')) {
        li.remove();
    }
});

์—ฐ์Šต ๋ฌธ์ œ

๋ฌธ์ œ 1: ์•„์ฝ”๋””์–ธ ๋ฉ”๋‰ด

ํด๋ฆญํ•˜๋ฉด ๋‚ด์šฉ์ด ์—ด๋ฆฌ๊ณ  ๋‹ซํžˆ๋Š” ์•„์ฝ”๋””์–ธ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
const accordion = document.querySelector('.accordion');

accordion.addEventListener('click', (e) => {
    const header = e.target.closest('.accordion-header');
    if (!header) return;

    const item = header.parentElement;
    const content = item.querySelector('.accordion-content');

    // ๋‹ค๋ฅธ ํ•ญ๋ชฉ ๋‹ซ๊ธฐ (์„ ํƒ์‚ฌํ•ญ)
    document.querySelectorAll('.accordion-item').forEach(other => {
        if (other !== item) {
            other.classList.remove('open');
        }
    });

    // ํ˜„์žฌ ํ•ญ๋ชฉ ํ† ๊ธ€
    item.classList.toggle('open');
});

๋ฌธ์ œ 2: ๊ธ€์ž ์ˆ˜ ์นด์šดํ„ฐ

textarea์— ์ž…๋ ฅํ•  ๋•Œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ธ€์ž ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
const textarea = document.querySelector('textarea');
const counter = document.querySelector('.counter');
const maxLength = 200;

textarea.addEventListener('input', (e) => {
    const length = e.target.value.length;
    counter.textContent = `${length} / ${maxLength}`;

    if (length > maxLength) {
        counter.classList.add('error');
    } else {
        counter.classList.remove('error');
    }
});

๋‹ค์Œ ๋‹จ๊ณ„


์ฐธ๊ณ  ์ž๋ฃŒ

to navigate between lessons