Written by coh at home

[혼공스] 6주차 Ch7, 8 본문

Etc/도서

[혼공스] 6주차 Ch7, 8

och 2025. 8. 24. 22:19

숙제

p352 누적예제를 활용하여 본인의 할 일 목록을 만들어 캡쳐하기

<!DOCTYPE html>
<html lang="en">
<head>
<script>

document.addEventListener('DOMContentLoaded', () => {

    const todoList = document.querySelector('#todo-list')
    const input = document.querySelector('#todo')
    const addBtn = document.querySelector('#add-button')

    let keyCount = 0;
    const addTodo = () => {
        if (input.value.trim() === '') {
            alert ('Add your todo-list')
            return
        }
        const item = document.createElement('div')
        const checkBox = document.createElement('input')
        const text = document.createElement('span')
        const button = document.createElement('button')

        const key = keyCount
        keyCount += 1

        item.setAttribute('data-key', key)
        item.appendChild(checkBox)
        item.appendChild(text)
        item.appendChild(button)
        todoList.appendChild(item)

        checkBox.type = 'checkbox'
        checkBox.addEventListener('change', (e) => {
            item.style.textDecoration = e.target.checked ? 'line-through' : ''
        })

        text.textContent = input.value;
        button.textContent = 'remove'
        button.addEventListener('click', (e) => {
            // item.parentNode.removeChild(item)
            removeTodo(key);
        })

        input.value =''

    }
    const removeTodo = (key) => {
            const item = document.querySelector(`[data-key="${key}"]`)
            todoList.removeChild(item);
        }

    addBtn.addEventListener('click', addTodo)
        input.addEventListener('keyup', (e) => {
            const ENTER = 13; 
            if (e.keyCode === ENTER)
                addTodo()
        })
})
</script>
<style>
    h1 { user-select: none;}
</style>
</head>
<body>
    <h1>할 일 목록</h1>
    <input id="todo" />
    <button id="add-button">ADD</button>
    <div id="todo-list"></div>
</body>
</html>

ch7 문서책체

html의 태그들을 자바스크립트에서는 document object라고 부른다. 이런 태그들로 만든 형태를 문서객체모델(DOM)이라고 한다.

DOMContentLoaded

<!DOCTYPE html>
<html>
    <head>
        <title>Doc</title>
    </head>
    <body>
    </body>
</html>

웹브라우저의 실행 순서는 다음과 같다.

  • : html5문서구나
  • html 태그 만들어야지
  • head 태그 만들어야지
  • title 태그가 있으니 반영해야지
  • body 태그가 있으니 반영해야지

만약 body태그가 만들어지지 않았는데 body에 무언가를 출력하려고 하면 문제가 발생한다.
따라서 이를 해결하기 위해서, 웹브라우저가 문서 객체를 모두 읽고 나서 실행하는 이벤트가 DOMContentLoaded이다.

documnet.addEventListener('DOMContentLoaded', () => {
    const h1 = (text) => `<h1>${text}</h1>`;
    document.body.innerHTML += h1('hello');
});

문서객체 가져오기

head, body, title태그를 제외한 다른 요소들은 querySelector라는 메서드를 통해서 요소를 가져올 수 있다.

document.body
document.head
document.title

document.querySelector(선택자) // 문서객체 한 개
document.querySelctorAll(선택자) // 문서객체 여러개 

선택자 부분에는 CSS 선택자를 입력해야한다. 기본적인 CSS 선택자는 다음과 같다.

이름 선택자형태 설명
태그 선택자 태그 특정 태그를 가진 요소 추출
아이디 선택자 #아이디 특정 id 속성을 가진 요소를 추출
클래스 선택자 .클래스 특정 class 속성을 가진 요소를 추출
속성 선택자 [속성=값] 특정 속성 값을 갖고 있는 요소를 추출
후손 선택자 선택자_A 선택자_B 선택자_A 아래의 선택자_B를 선택
  • querySelector
    문서 객체 한 개 선택.
document.addEventListener('DOMContentLoaded', () => {
        const header = document.querySelector('h1')

        header.textContent = 'HEADER'
        header.style.color = 'white'
        header.style.backgroundColor = 'black'
        header.style.padding = '10px'
    })
  • querySelectorAll
    문서객체 여러 개 선택. forEach문을 사용해서 활용한다.
document.addEventListener('DOMContentLoaded', () => {
        const header = document.querySelectorAll('h1')

        header.forEach((header, idx) => {
            header.textContent = 'HEADER_' + idx
            header.style.color = 'white'
            header.style.backgroundColor = 'black'
            header.style.padding = '10px'
        })
    })

글자 조작하기

속성이름 설명
문서객체.innerHTML 입력된 문자열을 html형식으로 넣는다
문서객체.textContent 입력된 문자열 그대로 넣는다
    document.addEventListener('DOMContentLoaded', () => {
        const a = document.querySelector('#a')
        const b = document.querySelector('#b')

        a.textContent = '<h1>textContent</h1>'
        b.innerHTML = '<h1>innerHTML</h1>'
    })

속성 조작하기

메서드를 통해 문서 객체의 속성을 조작할 수 있다. class, src, id 등 속성을 부여하거나 가져올 수 있다.
setAttribute(속성이름, 값)
getAttribute(속성이름)

    <img class="rect" />
    <img class="rect" />
    <img class="rect" />
    <img class="rect" />
...

document.addEventListener('DOMContentLoaded', () => {
        const rects = document.querySelectorAll('.rect');

        rects.forEach((rect, idx) => {
            const width = (idx + 1) * 100;
            const src = `http//placekitten.com/${width}/250`
            rect.setAttribute('src', src);
        })
    })

스타일 조작하기

문서 객체의 스타일을 먹이기 위해서 style 속성을 사용한다. CSS파일을 이용할 때와 이름이 살짝 다르다. 낙타표기법을 사용한다.
text-align -> textAlign 이렇게!

속성을 먹이는 방법은 다음과 같다.
h1.style.backgroundColor = 'black'

    document.addEventListener('DOMContentLoaded', () => {
        const divs = document.querySelectorAll('div')

        divs.forEach((div, idx) => {
            console.log(div, idx)
            const val = idx * 10;
            div.style.height = `10px`
            div.style.backgroundColor = `rgba(${val}, ${val}, ${val})`;
        });
    });

문서객체 생성하기

document.createElement(문서객체이름) 메서드를 통해 문서객체를 생성할 수 있고 이것을 어떤 요소 아래에 달 건지를 결정해야한다. 트리구조와 똑같다. 따라서 appendChild메서드가 있다.

부모객체.appendChild(자식객체);

document.addEventListener('DOMContentLoaded', () => {
    const h1 = document.createElement('h1');
    h1.textContent = 'hello';
    h1.style.color = 'white';
    h1.style.backgroundColor = 'black';
    h1.setAttribute("data-custom", '사용자 정의속성');
    document.body.appendChild(h1);
})

문서객체 이동하기

appendChild()는 문서객체를 이동할 때도 사용할 수 있다. 해당 객체에 대한 부모는 언제나 하나여야 한다.
따라서 문서객체를 다른 문서객체에 추가하면 문서 객체가 이동한다.

document.addEventListener('DOMContentLoaded', () => {

    const divA = document.querySelector('#first')
    const divB = document.querySelector('#second')

    const h1 = document.createElement('h1')
    h1.textContent = '이동하는 h1 tag'

    const toFirst = () => {
        divA.appendChild(h1);
        setTimeout(toSecond, 1000)
    }

    const toSecond = () => {
        divB.appendChild(h1);
        setTimeout(toFirst, 2000)

    }
    toFirst()
})

문서객체 제거하기

부모객체.removeChild(자식객체)

자식객체.parentNode.removeChild(자식객체)
자식은 부모에 parentNode를 통해 접근할 수 있다. 그래서 일반적으로 위와 같은 코드를 많이 사용한다.

    setTimeout(() => {
        const h1 = document.querySelector('h1')
        h1.parentNode.removeChild(h1);
    }, 3000);

이벤트 설정하기

addEventListener를 통해서 진행한다.
user-select:none 으로 지정하면, 해당태그를 마우스로 드래그할 수 없다.

<script>
document.addEventListener('DOMContentLoaded', () => {
    let count = 0;

    const h1 = document.querySelector('h1');
    h1.addEventListener('click', (e) => {
        count++;
        h1.textContent = count;
    });
})
</script>
<style>
    h1 { user-select: none;}
</style>

이벤트 제거하기

removeEventListener를 통해서 제거한다.
문서객체.removeEventListener(이벤트이름, 이벤트리스너)

document.addEventListener('DOMContentLoaded', () => {
    let count = 0;
    let isConnect = false;

    const h1 = document.querySelector('h1');
    const p = document.querySelector('p');
    const connectButton = document.querySelector('#connect')
    const disconnectButton = document.querySelector('#disconnect')

    const listener = (e) => {
        h1.textContent = `click:${count++}`;
    }

    connectButton.addEventListener('click', () => {
        if (isConnect === false) {
            isConnect = true;
            h1.addEventListener('click', listener);
            p.textContent = `이벤트상태:${isConnect}`
        }
    })

    disconnectButton.addEventListener('click', () => {
        if (isConnect === true) {
            isConnect = false;
            h1.removeEventListener('click', listener);

            p.textContent = `이벤트상태:${isConnect}`

        }
    })
})

이벤트 모델

사전 html기본을 알고 있어야 한다. 이제 지금까지 배운 것을 전부 활용해보자

이벤트를 연결하는 방법을 이벤트 모델이라고 한다. 이건 두 가지로 나뉜다.
표준이벤트 모델 : addEvenetListener
고전이벤트 모델 : 문서객체 onxx 시작하는 속성에 이벤트 연결하는 것
인라인이벤트 모델 : html요소에 함수를 넣는 것

const listener = (event) => {...}
<div onkeyup="listener(event)" ></div>

사실 표준이벤트를 많이 쓰고, 요즘은 인라인이벤트도 조금씩 쓰고 있다.

키보드 이벤트

이벤트 설명
keydown 키가 눌릴 때 실행. 꾹 누를 때, 입력될 때. 한국어 처리는 못할 때가 있음
keypress 키가 입력되었을 때 실행. 아시아권 문자는 처리 못할 때도 있음
keyup 키보드에서 키가 떨어질 때 실행

일반적으로 keyup을 사용한다. 하지만 꾹 눌렀을 때 글씨를 세지 못하는 문제가 있다.

document.addEventListener('DOMContentLoaded' () => {
    const textArea = document.querySelector('textarea');
    const h1 = document.querySelector('h1');

    textArea.addEventListener('keyup', (e) => {
        const length = textArea.value.length
        h1.textContent = `글자수:${length}`
    })
})

키보드 키코드

키보드 이벤트가 발생할 때는 이벤트 객체로 어떤 키를 눌렀는지를 알 수 있다.

이벤트 속성 이름 설명
code 입력한 키
keyCode 입력한 키를 나타내는 숫자
altKey alt키를 눌렀는지
ctrlKey ctrl키를 눌렀는지
shiftKey shift키를 눌렀는지

code 속성은 입력한 키에 대한 문자열이 들어있고 altKey, ctrlKey 등은 boolean값이 들어있다.

<!DOCTYPE html>
<html lang="en">
<head>
<script>
document.addEventListener('DOMContentLoaded', () => {
    const h1 = document.querySelector('h1')
    const print = (e) => {
        let output = ''
        output += `alt:${e.altKey}<br>`
        output += `ctrl:${e.ctrlKey}<br>`
        output += `shift:${e.shiftKey}<br>`
        output += `code:${typeof(e.code) !== 'undefined' ? e.code : e.keyCode}<br>`
        h1.innerHTML = output;
    }

    document.addEventListener('keydown', print)
    document.addEventListener('keyup', print)
})
</script>
<style>
    h1 { user-select: none;}
</style>
</head>
<body>
    <hr>
    <textarea></textarea>
    <h1></h1>
    <hr>
</body>
</html>

별을 이동시키는 게임을 구현할 때는 keydown을 많이 사용한다. 꾹누르는 것을 지원해줘야하기 때문.

document.addEventListener('DOMContentLoaded', () => {
    const star = document.querySelector('h1')
    star.style.position = 'absolute'

    let [x, y] = [0, 0]
    const block = 20
    const print = () => {
        star.style.left = `${x * block}px`
        star.style.top = `${y * block}px`
    }
    print()

    const [left, up, right, down] = [37,38,39,40]
    document.body.addEventListener('keydown', (e) => {
        switch (e.keyCode)
        {
            case left:
                x -= 1
                break
            case up:
                y -= 1
                break
            case right:
                x += 1
                break
            case down:
                y += 1
                break
        }
        print()
    })
})

이벤트 발생객체

이벤트 리스너 함수를 외부로 빼낸 경우 변수 접근 문제가 생길 수 있습니다. 이럴 때 어떻게 해결할까용

  1. event.currentTarget 속성을 사용합니다.
  2. this키워드를 사용합니다.

화살표 함수가 없을 때는 2번째 방법을 많이 사용했지만, 지금은 둘 다 많이 사용합니다. 라이브러리와 프레임워크에 따라 선호하는 방식이 다릅니다.

const listener = (event) => {
    const len = event.currentTarget.value.length
    const h1 = document.querySelector('h1')
    h1.textContent = `글자수:${len}`
}

document.addEventListener('DOMContentLoaded', () => {
    const textArea = document.querySelector('textarea')
    textArea.addEventListener('keyup', listener)
})

글자 입력 양식 이벤트

사용자로부터 어떠한 입력을 받을 때 사용하는 요소를 form이라고 합니다.
input, textarea, button, select 태그 등이 모두 입력양식입니다.

document.addEventListener('DOMContentLoaded', () => {
    const input = document.querySelector('input')
    const button = document.querySelector('button')
    const p = document.querySelector('p')

    button.addEventListener('click', () => {
        const inch = Number(input.value)

        if (isNaN(inch))
        {
            p.textContent = 'not a number'
            return
        }
        const cm = inch * 2.54
        p.textContent = cm;
    })
})

들여쓰기를 줄이면 코드가 깔끔해집니다. else문은 최대한 안쓰는 게 좋죠.

드롭다운

select 태그와 option태그를 통해 구현할 수 있습니다. 이건, value도 사용이 가능하긴 한데, 보통 options와 index를 통해 접근하는게 더 안전한 거 같습니다.

document.addEventListener('DOMContentLoaded', () => {
    const select = document.querySelector('select')
    const p = document.querySelector('p')

    select.addEventListener('change', (e) => {
        const value = e.currentTarget.value;
        const idx = e.currentTarget.selectedIndex;

        p.textContent = `choice:${value}`
    })
})

여기서 multiple 속성을 부여하면 select박스의 아이템을 여러개 선택할 수 있습니다. 보통 체크박스가 낫습니다.

ch8 예외처리