자기계발/혼공단

[자기계발] 혼공단10기 자바스크립트 6주차 (문서 객체 모델, 예외 처리)

mylee99 2023. 8. 18. 12:59
6주차(8/14-8/20)
Chapter 07 ~ 08

 

Chapter 07. 문서 객체 모델

문서 객체 모델은 넓은 의미로 웹 브라우저가 HTML 페이지를 인식하는 방식이고, 좁은 의미로 document 객체와 관련된 객체의 집합임. 이를 사용하면 HTML 페이지에 태그를 추가, 수정, 제거할 수 있음.

 

1. 문서 객체 조작하기

- 요소(element) : HTML 페이지에 있는 html, head, body, title, h1, div, span 등.

- 이를 JS에서는 문서 객체(document object)라고 부름. 따라서 '문서 객체를 조작한다'는 말은 'HTML 요소들을 조작한다'

문서 객체 모델(DOM, Document Objects Model) : 문서 객체를 조합해서 만든 전체적인 형태

 

1) DOMContentLoaded 이벤트

문서 객체를 조작할 때는 DOMContentLoaded 이벤트를 사용함.

"document라는 문서 객체의 DOMContentLoaded 이벤트가 발생했을 때, 매개변수로 지정한 콜백 함수를 실행해라"

document.addEventListener('DOMContentLoaded', () => {
	//문장
}

DOMContentLoaded 이벤트는 웹 브라우저가 문서 객체를 모두 읽고 나서 실행하는 이벤트이기 때문에, body 태그가 생성되기 이전인 head 태그 내부에 script 태그를 배치해도 DOMContentLoaded 상태가 되었을 때 콜백 함수를 호출함.

<!DOCTYPE html>
<head>
    <title>DOMContentLoaded</title>
    <script>
        //DOMContentLoaded 이벤트를 연결
        document.addEventListener('DOMContentLoaded', () => {
            const h1 = (text) => `<h1>${text}</h1>`
            document.body.innerHTML += h1('DOMContentLoaded 이벤트 발생')
        })
    </script>
</head>
<body>

</body>
</html>

 

2) 문서 객체 가져오기

document.body 코드를 사용하면 문서의 body 요소를 읽어 들일 수 있음. 이외에도 HTML 문서에 있는 head 요소와 title 요소 등은 다음과 같은 방법으로 읽어들일 수 있음.

document.head
document.body
document.title

head 요소와 body 요소 내부에 만든 다른 요소들은 다음과 같은 별도의 메소드를 사용해서 접근함.

document.querySelector(선택자)
document.querySelectAll(선택자)

선택자 부분에서는 CSS 선택자를 입력함.

이름 선택자 형태 설명
태그 선택자 태그 특정 태그를 가진 요소를 추출함.
아이디 선택자 #아이디 특정 id 속성을 가진 요소를 추출함.
클래스 선택자 .클래스 특정 class 속성을 가진 요소를 추출함.
속성 선택자 [속성=값] 특정 속성 값을 갖고 있는 요소를 추출함.
후손 선택자 선택자_A 선택자_B 선택자_A 아래에 있는 선택자_B를 선택함.

querySelector() 메소드는 요소를 하나만 추출하고, querySelectorAll() 메소드는 문서 객체를 여러 개 추출함.

querySelector() 메소드를 사용해서 h1 태그를 추출하고 조작하는 예제 생성

<script>
    document.addEventListener('DOMContentLoaded', () => {
        //요소를 읽어들임
        const header = document.querySelector('h1')

        //텍스트와 스타일 변경
        header.textContent = 'HEADERS'
        header.style.color = 'white'
        header.style.backgroundColor = 'black'
        header.style.padding = '10px'
    })
</script>
<body>
    <h1></h1>
</body>

querySelectorAll() 메소드는 내부의 요소에 접근하고 활용하기 위해 일반적으로 forEach() 메소드로 반복을 돌려 사용함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        //요소를 읽어들임
        const headers = document.querySelectorAll('h1')

        //텍스트와 스타일 변경
        headers.forEach((header) => {
            header.textContent = 'HEADERS'
            header.style.color = 'white'
            header.style.backgroundColor = 'black'
            header.style.padding = '10px'
        })
    })
</script>
<body>
    <h1></h1>
    <h1></h1>
    <h1></h1>
    <h1></h1>
</body>

 

3) 글자 조작하기

문서 객체.textContent : 입력된 문자열을 그대로 넣음.

문서 객체.innerHTML : 입력된 문자열을 HTML 형식으로 넣음.

아래 예제의 textContent 속성은 글자가 그대로 들어가지만, innerHTML 속성은 <h1>~<h1>을 요소로 변환해서 들어감.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const a = document.querySelector('#a')
        const b = document.querySelector('#b')

        a.textContent = '<h1>textContent 속성</h1>'
        b.innerHTML = '<h1>innerHTML 속성</h1>'
    })
</script>
<body>
    <div id="a"></div>
    <div id="b"></div>
</body>

 

4) 속성 조작하기

문서 객체.setAttribute(속성 이름, 값) : 특정 속성에 값을 지정함.

문서 객체.getAttribute(속성 이름) : 특정 속성을 추출함.

아래 예제에서 index 값은 [0, 1, 2, 3]이 반복됨. 1을 더해서 [1, 2, 3, 4]가 되게 만들고, 100을 곱해서 너비가 [100, 200, 300, 400]이 되게 만들었음.

 

6주차 기본 미션
p.315의 <직접 해보는 손코딩>을 실행한 후 출력되는 고양이 이미지 캡처하기

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const rects = document.querySelectorAll('.rect')   //특정 클래스로 요소 선택

        rects.forEach((rect, index) => {
            const width = (index + 1) * 100
            const src = `http://placekitten.com/${width}/250`
            rect.setAttribute('src', src)	//rect.src = src로 줄일 수 있음
        })
    })
</script>
<body>
    <img class="rect">
    <img class="rect">
    <img class="rect">
    <img class="rect">
</body>

 

5) 스타일 조작하기

문서 객체의 스타일을 조작할 때는 style 속성을 사용함. style 속성은 객체이며, 내부에는 속성으로 CSS를 사용해서 지정할 수 있는 스타일들이 있음.

아래 예제에서는 25개의 div 태그를 조작해서 검은색에서 흰색으로 변화하는 그레이디언트를 생성함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const divs = document.querySelectorAll('body > div')    //body 태그 아래에 있는 div 태그 선택

        divs.forEach((div, index) => {      //div 개수만큼 반복하여 출력
            console.log(div, index)
            const val = index * 10          //인덱스는 0부터 24까지 반복
            div.style.height = `10px`       //크기를 지정할 때는 반드시 단위를 함께 붙여줌
            div.style.backgroundColor = `rgba(${val}, ${val}, ${val})`
        })
    })
</script>
<body>
    <!-- div 태그 25개 -->
    <div></div><div></div><div></div><div></div><div></div>
    <div></div><div></div><div></div><div></div><div></div>
    <div></div><div></div><div></div><div></div><div></div>
    <div></div><div></div><div></div><div></div><div></div>
    <div></div><div></div><div></div><div></div><div></div>
</body>

 

6) 문서 객체 생성하기

문서 객체를 생성하고 싶을 때에는 document.createElement() 메소드를 사용함.

document.createElement(문서 객체 이름)

어떤 문서 객체가 있을 때 위에 있는 것을 부모(parent)라고 부르고, 아래에 있는 것을 자식(child)이라고 부름.

문서 객체에는 appendChild() 메소드가 있으며, 이를 활용하면 어떤 부모 객체 아래에 자식 객체를 추가할 수 있음.

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

아래 예제에서는 document.createElement() 메소드로 h1 태그를 생성하고, 이를 document.body 태그 아래에 추가함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        //문서 객체 생성하기
        const header = document.createElement('h1')

        //생성한 태그 조작하기
        header.textContent = '문서 객체 동적으로 생성하기'
        header.setAttribute('data-custom', '사용자 정의 속성')
        header.style.color = 'white'
        header.style.backgroundColor = 'black'

        //h1 태그를 body 태그 아래에 추가하기
        document.body.appendChild(header)
    })
</script>
<body>

</body>

 

7) 문서 객체 이동하기

appendChild() 메소드는 문서 객체를 이동할 때도 사용할 수 있음.

문서 객체의 부모는 언제나 하나여야 하기 때문에, 문서 객체를 다른 문서 객체에 추가하면 문서 객체가 이동함.

아래 예제에서는 1초마다 <h1>이동하는 h1 태그</h1>라는 요소가 div#first(id 속성이 first인 div 태그)와 div#second로 움직이게 함. setTimeout() 함수를 번갈아가면서 실행, h1 태그가 hr 태그를 기준으로 위와 아래로 이동함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        //문서 객체 읽어들이고 생성하기
        const divA = document.querySelector('#first')
        const divB = document.querySelector('#second')
        const h1 = document.createElement('h1')
        h1.textContent = '이동하는 h1 태그'

        //서로 번갈아가면서 실행하는 함수 구현
        const toFirst = () => {
            divA.appendChild(h1)
            setTimeout(toSecond, 1000)
        }
        const toSecond = () => {
            divB.appendChild(h1)
            setTimeout(toFirst, 1000)
        }
        toFirst()
    })
</script>
<body>
    <div id="first">
        <h1>첫번째 div 태그 내부</h1>
    </div>
    <hr>
    <div id="second">
        <h1>두번째 div 태그 내부</h1>
    </div>
</body>

 

8) 문서 객체 제거하기

문서 객체를 제거할 때는 removeChild() 메소드를 사용함.

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

appendChild() 메소드 등으로 부모 객체와 이미 연결이 완료된 문서 객체의 경우 parentNode 속성으로 부모 객체에 접근할 수 있으므로, 일반적으로 어떤 문서 객체를 제거할 때는 다음과 같은 형태의 코드를 사용함.

문서 객체.parentNode.removeChild(문서 객체)

아래 예제에서는 특정 개체를 간단하게 실행하고 3초 후에 화면에서 h1 태그를 제거함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        setTimeout(() => {
            const h1 = document.querySelector('h1')
            h1.parentNode.removeChild(h1)       //h1 태그의 부모 객체 body 태그에 접근하여 제거
            //document.body.removeChild(h1)     //h1.parentNode가 document.body이므로, 이런 형태로도 제거 가능
        }, 3000)
    })
</script>
<body>
    <hr>
    <h1>제거 대상 문서 객체</h1>
    <hr>
</body>

 

9) 이벤트 설정하기

모든 문서 객체는 생성되거나 클릭되거나 마우스를 위에 올리거나 할 때 이벤트라는 것이 발생함.

그리고 이 이벤트가 발생할 때 실행할 함수는 addEventListener() 메소드를 사용함.

문서 객체.addEventListener(이벤트 이름, 콜백 함수)

이벤트가 발생할 때 실행할 함수를 이벤트 리스너 또는 이벤트 핸들러라고 부름.

다음 예제는 addEventListener() 메소드를 사용해서 h1 태그를 클릭할 때 이벤트 리스너(콜백 함수)를 호출함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        let counter = 0
        const h1 = document.querySelector('h1')

        h1.addEventListener('click', (event) => {
            counter++
            h1.textContent = `클릭 횟수: ${counter}`
        })
    })
</script>
<style>
    h1 {
        /*클릭을 여러번 했을 때 글자가 선택되는 것을 막기 위한 스타일*/
        user-select: none;
    }
</style>
<body>
    <h1>클릭 횟수: 0</h1>
</body>

이벤트를 제거할 때는 다음과 같은 형태로 removeEventListener() 메소드를 사용함.

문서 객체.removeEventListener(이벤트 이름, 이벤트 리스너)

이벤트 리스너 부분에는 연결할 때 사용했던 이벤트 리스너를 넣음.

변수 또는 상수로 이벤트 리스너를 미리 만들고, 이를 이벤트 연결과 연결 제거에 활용함.

아래 예제에서는 버튼으로 이벤트 연결 상태를 제어하는 프로그램을 만들고, 이벤트 리스너가 여러 번 연결되지 않게 isConnect라는 변수를 활용함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        let counter = 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 = (event) => {
            h1.textContent = `클릭 횟수: ${counter++}`
        }

        connectButton.addEventListener('click', () => {
            if(isConnect === false) {
                h1.addEventListener('click', listener)
                p.textContent = '이벤트 연결 상태: 연결'
                isConnect = true
            }
        })
        disconnectButton.addEventListener('click', () => {
            if(isConnect === true) {
                h1.removeEventListener('click', listener)
                p.textContent = '이벤트 연결 상태: 해제'
                isConnect = false
            }
        })
    })
</script>
<style>
    h1 {
        /*클릭을 여러번 했을 때 글자가 선택되는 것을 막기 위한 스타일*/
        user-select: none;
    }
</style>
<body>
    <h1>클릭 횟수: 0</h1>
    <button id="connect">이벤트 연결</button>
    <button id="disconnect">이벤트 제거</button>
    <p>이벤트 연결 상태 : 해제</p>
</body>

 

 

2. 이벤트 활용

1) 이벤트 모델

이벤트 모델 : 이벤트를 연결하는 방법

addEventListener() 메소드를 사용하는 방법이 현재 표준으로 사용하고 있는 방법이므로 표준 이벤트 모델이라고 부름.

document.body.addEventListener('keyup', () => {

})

과거에는 다음과 같이 문서 객체가 갖고 있는 onOO으로 시작하는 속성에 함수를 할당해서 이벤트를 연결했고, 이와 같은 이벤트 연결 방법을 고전 이벤트 모델이라고 부름.

document.body.onkeyup = (event) => {

}

고전 이벤트 모델처럼 onOO으로 시작하는 속성을 HTML 요소에 직접 넣어서 이벤트를 연결하는 것을 인라인 이벤트 모델이라고 부름.

<script>
	const listener = (event) => {
    
    }
</script>
<body onkeyup="listener(event)">

</body>

모든 이벤트 모델의 이벤트 리스너는 첫 번째 매개변수로 이벤트 객체를 받음.

 

1) 키보드 이벤트

- keydown : 키가 눌릴 때 실행됨. 키보드를 꾹 누르고 있을 때도, 입력될 때도 실행됨.

- keypress : 키가 입력되었을 때 실행됨. 하지만 웹 브라우저에 따라서 아시아권의 문자(한국어, 중국어, 일본어)를 제대로 처리하지 못하는 문제가 있음(공백이 들어가기 전까지는 글자 수를 세지 않음).

- keyup : 키보드에서 키가 떨어질 때 실행됨. (일반적으로 사용됨)

아래 예제에서는 키보드 이벤트로 입력 양식의 글자 수를 세는 프로그램을 만듦.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const textarea = document.querySelector('textarea')
        const h1 = document.querySelector('h1')

        textarea.addEventListener('keyup', (event) => {
            const length = textarea.value.length
            h1.textContent = `글자 수: ${length}`
        })
    })
</script>
<body>
    <h1></h1>
    <textarea></textarea>
</body>

* 키보드 키 사용하기

키보드 이벤트가 발생할 때는 이벤트 객체로 어떤 키를 눌렀는지와 관련된 속성들이 따라옴.

- code : 입력한 키

- keyCode : 입력한 키를 나타내는 숫자

- altKey : Alt 키를 눌렀는지

- ctrlKey : Ctrl 키를 눌렀는지

- shiftKey : Shift 키를 눌렀는지

아래 예제는 keydown 이벤트와 keyup 이벤트가 발생할 때 표에서 설명한 속성을 모두 출력하는 프로그램.

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

        document.addEventListener('keydown', print)
        document.addEventListener('keyup', print)
    })
</script>
<body>
    <h1></h1>
</body>

아래 예제는 keyCode 속성(입력한 키를 숫자로 나타냄)을 활용해 방향키로 별을 이동시키는 코드를 만듦.

<script>
    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', (event) => {
            switch(event.keyCode) {
                case left:
                    x -= 1
                    break
                case up:
                    y -= 1
                    break
                case right:
                    x += 1
                    break
                case down:
                    y += 1
                    break
            }
            print()
        })
    })
</script>
<body>
    <h1>★</h1>
</body>

 

2) 이벤트 발생 객체

이벤트 리스너를 외부로 분리하는 경우는 이벤트를 발생시킨 객체에 접근하는 방법이 다름.

1) event.currentTarget 속성을 사용 : () => { }와 function () { } 형태 모두 사용이 가능함.

2) this 키워드를 사용 : 화살표 함수가 아닌 function() { } 형태로 함수를 선언한 경우에 사용함.

//event.currentTarget을 사용
const length = event.currentTarget.value.length
//this 키워드를 사용
const length = this.value.length

 

3) 글자 입력 양식 이벤트

입력 양식 : 사용자로부터 어떠한 입력을 받을 때 사용하는 요소 (HTML에서는 input 태그, textarea 태그 등)

<!-- 입력 양식을 기반으로 inch를 cm 단위로 변환하는 프로그램 -->
<script>
    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 = '숫자를 입력해주세요'
                return
            }
            //변환해서 출력
            const cm = inch * 2.54
            p.textContent = `${cm} cm`
        })
    })
</script>
<body>
    <input type="text"> inch<br>
    <button>계산</button>
    <p></p>
</body>
<!-- 이메일 형식을 확인하는 프로그램 -->
<script>
    document.addEventListener('DOMContentLoaded', () => {
        const input = document.querySelector('input')
        const p = document.querySelector('p')
        const isEmail = (value) => {
            //골뱅이를 갖고 있고 && 골뱅이 뒤에 점이 있다면
            return (value.indexOf('@') > 1)
                && (value.split('@')[1].indexOf('.') > 1)
        }
        input.addEventListener('keyup', (event) => {
            const value = event.currentTarget.value
            if(isEmail(value)) {
                p.style.color = 'green'
                p.textContent = `이메일 형식입니다: ${value}`
            } else {
                p.style.color = 'red'
                p.textContent = `이메일 형식이 아닙니다: ${value}`
            }
        })
    })
</script>
<body>
    <input type="text">
    <p></p>
</body>

일반적으로 이런 유효성 검사를 할 때에는 정규 표현식(regular expression)을 사용함.

 

- 드롭다운 목록 활용하기

기본적으로 select 태그로 구현함.

아래 예제는 select 태그를 사용해서 드롭다운 목록을 만들고 선택했을 때(값이 변경되었을 때) 선택한 것을 출력함.

코드를 실행하고 드롭다운 목록에서 항목을 선택하면 options[index]에서 선택한 option 태그가 출력됨.

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

        select.addEventListener('change', (event) => {
            const options = event.currentTarget.options
            const index = event.currentTarget.options.selectedIndex

            p.textContent = `선택: ${options[index].textContent}`
        })
    })
</script>
<body>
    <select>
        <option>떡볶이</option>
        <option>순대</option>
        <option>오뎅</option>
        <option>튀김</option>
    </select>
    <p>선택: 떡볶이</p>
</body>

select 태그에 multiple 속성을 부여하면 Ctrl 키 또는 Shift 키를 누르고 여러 항목을 선택할 수 있는 선택 상자가 나옴.

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

        select.addEventListener('change', (event) => {
            const options = event.currentTarget.options
            const list = []
            for (const option of options) {
                if (option.selected) {
                    list.push(option.textContent)
                }
            }
            p.textContent = `선택: ${list.join(',')}`
        })
    })
</script>
<body>
    <select multiple>
        <option>떡볶이</option>
        <option>순대</option>
        <option>오뎅</option>
        <option>튀김</option>
    </select>
    <p></p>
</body>

아래 예제는 cm 단위를 여러 단위로 변환하는 프로그램

<script>
    document.addEventListener('DOMContentLoaded', () => {
        let 현재값
        let 변환상수 = 10

        const select = document.querySelector('select')
        const input = document.querySelector('input')
        const span = document.querySelector('span')

        const calculate = () => {
            span.textContent = (현재값 * 변환상수).toFixed(2)   //소수점 2번째 자리까지 출력
        }

        select.addEventListener('change', (event) => {
            const options = event.currentTarget.options
            const index = event.currentTarget.options.selectedIndex
            변환상수 = Number(options[index].value)
            calculate()
        })

        input.addEventListener('keyup', (evnet) => {
            현재값 = Number(event.currentTarget.value)
            calculate()
        })
    })
</script>
<body>
    <input type="text"> cm = 
    <span></span>
    <select>
        <option value="10">mm</option>
        <option value="0.01">m</option>
        <option value="0.393701">inch</option>
    </select>
</body>

 

- 체크 박스 활용하기

체크 상태를 확인할 때는 입력 양식의 checked 속성을 사용함.

아래 예제는 체크 상태일 때만 타이머를 증가시키는 프로그램. 

change 이벤트가 발생했을 때 체크 박스의 체크 상태를 확인하고 setInterval() 함수 또는 clearInterval() 함수를 실행함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        let [timer, timerId] = [0, 0]
        const h1 = document.querySelector('h1')
        const checkbox = document.querySelector('input')

        checkbox.addEventListener('change', (event) => {
            if (event.currentTarget.checked) {
                //체크 상태
                timerId = setInterval(() => {
                    timer += 1
                    h1.textContent = `${timer}초`
                }, 1000)
            } else {
                //체크 해제 상태
                clearInterval(timerId)
            }
        })
    })
</script>
<body>
    <input type="checkbox">
    <span>타이머 활성화</span>
    <h1></h1>
</body>

 

- 라디오 버튼 활용하기

라디오 버튼은 성별을 선택할 때와 같이 선택안함, 여성, 남성이 있다면 이 중에서 하나만 선택할 수 있게 해주는 요소임.

체크박스와 마찬가지로 checked 속성을 사용함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        //문서 객체 추출하기
        const output = document.querySelector('#output')
        const radios = document.querySelectorAll('[name=pet]')

        //모든 라디오 버튼에
        radios.forEach((radio) => {
            //이벤트 연결
            radio.addEventListener('change', (event) => {
                const current = event.currentTarget
                if (current.checked) {
                    output.textContent = `좋아하는 애완동물은 ${current.value}이시군요!`
                }
            })
        })
    })
</script>
<body>
    <h3># 좋아하는 애완동물을 선택해주세요</h3>
    <input type="radio" name="pet" value="강아지">
    <span>강아지</span>
    <input type="radio" name="pet" value="고양이">
    <span>고양이</span>
    <input type="radio" name="pet" value="햄스터">
    <span>햄스터</span>
    <input type="radio" name="pet" value="기타">
    <span>기타</span>
    <hr>
    <h3 id="output"></h3>
</body>

 

4) 기본 이벤트 막기

웹 브라우저는 이미지에서 마우스 오른쪽 버튼을 클릭하면 컨텍스트 메뉴를 출력함.

이처럼 어떤 이벤트가 발생했을 때 웹 브라우저가 기본적으로 처리해주는 것을 기본 이벤트라고 부름.

링크를 클릭했을 때 이동하는 것, 제출 버튼을 눌렀을 때 이동하는 것 등이 모두 기본 이벤트의 예임. 이러한 기본 이벤트를 제거할 때는 event 객체의 preventDefault() 메소드를 사용함.

아래 예제는 모든 img 태그의 contextmenu 이벤트가 발생할 때 preventDefault() 메소드를 호출해서 기본 이벤트를 막음.

인터넷에서 이미지 불펌 방지 등을 구현할 때 사용함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const imgs = document.querySelectorAll('img')

        imgs.forEach((img) => {
            img.addEventListener('contextmenu', (event) => {
                event.preventDefault()      //컨텍스트 메뉴를 출력하는 기본 이벤트를 제거함
            })
        })
    })
</script>
<body>
    <img src="http://placekitten.com/300/300" alt="">
</body>

아래 예제는 체크박스가 체크되어 있는 상태에서만 링크를 활성화함.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        let status = false

        const checkbox = document.querySelector('input')
        checkbox.addEventListener('change', (event) => {
            status = event.currentTarget.checked
        })

        const link = document.querySelector('a')
        link.addEventListener('click', (event) => {
            if(!status) {
                event.preventDefault()
            }
        })
    })
</script>
<body>
    <input type="checkbox">
    <span>링크 활성화</span>
    <br>
    <a href="http://hanbit.co.kr">한빛미디어</a>
</body>

 

6주차 선택 미션
p.352 누적 예제를 활용하여 본인의 할 일 목록을 만들어 캡처하기

<body>
    <h1>할 일 목록</h1>
    <input id="todo">
    <button id="add-button">추가하기</button>
    <div id="todo-list">


    </div>
</body>
<script>
document.addEventListener('DOMContentLoaded', () => {
      // 문서 객체를 가져옵니다.
      const input = document.querySelector('#todo')
      const todoList = document.querySelector('#todo-list')
      const addButton = document.querySelector('#add-button')

      // 변수를 선언합니다.
      let keyCount = 0

      // 함수를 선언합니다.
      const addTodo = () => {
        // 입력 양식에 내용이 없으면 추가하지 않습니다.
        if (input.value.trim() === '') {
          alert('할 일을 입력해주세요.')
          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 객체를 조작하고 추가합니다.
        item.setAttribute('data-key', key)
        item.appendChild(checkbox)
        item.appendChild(text)
        item.appendChild(button)
        todoList.appendChild(item)

        // checkbox 객체를 조작합니다.
        checkbox.type = 'checkbox'
        checkbox.addEventListener('change', (event) => {
          item.style.textDecoration
            = event.target.checked ? 'line-through' : ''
        })

        // text 객체를 조작합니다.
        text.textContent = input.value

        // button 객체를 조작합니다.
        button.textContent = '제거하기'
        button.addEventListener('click', () => {
          removeTodo(key)
        })

        // 입력 양식의 내용을 비웁니다.
        input.value = ''
      }

      const removeTodo = (key) => {
        // 식별 키로 문서 객체를 제거합니다.
        const item = document.querySelector(`[data-key="${key}"]`)
        todoList.removeChild(item)
      }

      // 이벤트 연결
      addButton.addEventListener('click', addTodo)
      input.addEventListener('keyup', (event) => {
        // 입력 양식에서 Enter 키를 누르면 바로 addTodo() 함수를 호출합니다.
        const ENTER = 13
        if (event.keyCode === ENTER) {
          addTodo()
        }
      })	
})
</script>

 

* 타이머로 구현한 남은 글자 수 세기

focus 이벤트와 blur 이벤트를 사용

입력 양식에 초점을 맞춘 경우(활성화 상태)와 초점을 해제한 경우(비활성화 상태)에 발생하는 이벤트

어떤 상황에서라도, 어떤 언어를 입력하더라도 글자 수를 정상적으로 출력할 수 있음.

<script>
    document.addEventListener('DOMContentLoaded', () => {
        const textarea = document.querySelector('textarea')
        const h1 = document.querySelector('h1')
        let timerId

        textarea.addEventListener('focus', (event) => {
            timerId = setInterval(() => {
                const length = textarea.value.length
                h1.textContent = `글자 수: ${length}`
            }, 50)
        })
        textarea.addEventListener('blur', (event) => {
            clearInterval(timerId)
        })
    })
</script>
<body>
    <h1></h1>
    <textarea></textarea>
</body>

 

* localStorage 객체

웹 브라우저가 기본적으로 제공하는 객체. 다음과 같은 메소드를 갖고 있음.

- localStorage.getItem(키) : 저장된 값을 추출함. 없으면 undefined가 나옴. 객체의 속성을 추출하는 일반적인 형태로 localStorage, 키 또는 localStorage[키] 형태로 사용할 수도 있음.

- localStorage.setItem(키, 값) : 값을 저장함. 이전과 마찬가지로 객체에 속성을 지정하는 일반적인 형태를 사용할 수도 있음.

- localStorage.removeItem(키) : 특정 키의 값을 제거함.

- localStorage.clear() : 저장된 모든 값을 제거함.

아래 예제에서는 웹 브라우저에 데이터를 저장하는 localStorage 객체를 활용함.

이처럼 웹 브라우저가 제공해주는 기능을 웹 API라고 부름.

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

        const savedValue = localStorage.getItem('input')
        //localStorage.input도 가능함
        if(savedValue) {
            input.value = savedValue
            p.textContent = `이전 실행 때의 마지막 값: ${savedValue}`
        }

        input.addEventListener('keyup', (event) => {
            const value = event.currentTarget.value
            localStorage.setItem('input', value)
            //localStorage.input = value도 가능함
        })

        button.addEventListener('click', (event) => {
            localStorage.clear()
            input.value = ''
        })
    })
</script>
<body>
    <p></p>
    <button>지우기</button>
    <input type="text">
</body>

 

 

Chapter 08. 예외 처리

1. 구문 오류와 예외

구문 오류 : 괄호 개수를 잘못 입력하는 등의 오류로 코드가 실행조차 되지 않는 오류

예외 : 이러한 문법적 오류를 제외하고 코드 실행 중간에 발생하는 오류, 이를 처리하는 것을 예외 처리

 

1) 오류의 종류

- 구문 오류(syntax error) : 프로그램 실행 전에 발생하는 오류

괄호의 짝을 맞추지 않았다든지, 문자열을 열었는데 닫지 않았다든지 할 떄 발생하는 오류

이러한 구문 오류가 있으면 웹 브라우저가 코드를 분석조차 하지 못하므로 실행되지 않음.

아래 예제는 console.log() 메소드를 입력할 때 마지막에 닫는 괄호를 입력하지 않아서 괄호가 제대로 닫히지 않음.

<script>
    //프로그램 시작 확인
    console.log("# 프로그램이 시작되었습니다!")

    //구문 오류가 발생하는 부분
    console.log("괄호를 닫지 않는 실수를 했습니다"      //Uncaught SyntaxError: missing ) after argument list
</script>

 

- 예외(exception) 또는 런타임 오류(runtime error) : 프로그램 실행 중에 발생하는 오류

아래 예제는 console.log() 메소드를 사용해야 하는데 console.rog()라고 잘못 입력한 상태

<script>
    //프로그램 시작 확인
    console.log("# 프로그램이 시작되었습니다!")

    //구문 오류가 발생하는 부분
    console.rog("log를 rog로 잘못 입력했습니다")        //Uncaught TypeError: console.rog is not a function
</script>

이처럼 실행 중에 발생하는 오류가 예외임. 자바스크립트에서는 SyntaxError라고 출력되는 오류 이외의 모든 오류(TypeError, ReferenceError, RangeError)가 예외로 분류됨.

 

2) 기본 예외 처리

조건문을 사용해서 예외가 발생하지 않게 만드는 것을 기본 예외 처리라고 부름.

아래 예제는 querySelector() 메소드로 문서 객체를 추출한 뒤 textContent 속성에 글자를 할당하는 코드지만, body 태그 내부에서 h1 태그가 없어 예외가 발생함.

<body>

</body>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        const h1 = document.querySelector('h1')
        h1.textContent = '안녕하세요'
    })
</script>

다음과 같이 조건문으로 h1이 존재하는 경우에만 textContent 속성을 변경하도록 예외 처리 가능

<body>

</body>
<script>
    document.addEventListener('DOMContentLoaded', () => {
        const h1 = document.querySelector('h1')
        if (h1) {
            h1.textContent = '안녕하세요'
        } else {
            console.log('h1 태그를 추출할 수 없습니다.')
        }
    })
</script>

 

3) 고급 예외 처리

이전 절에서 알아보았던 예외를 조금 더 쉽게 잡을 수 있는 기능으로 try catch finally 구분이 있음.

try 구문 안에서 예외를 발생하면 이를 catch 구문에서 처리함. finally 구문은 필수 사항은 아니며 예외 발생 여부와 상관없이 수행해야 하는 작업이 있을 때 사용함.

try {
	//예외가 발생할 가능성이 있는 코드
} catch (exception) {
	//예외가 발생했을 때 실행할 코드
} finally {
	//무조건 실행할 코드
}

 

2. 예외 처리 고급

1) 예외 객체(exception object)

예외가 발생하면 예외와 발생된 정보를 확인할 수 있게 해주는 것

try catch 구문을 사용할 때 catch의 괄호 안에 입력하는 식별자가 예외 객체이며, 일반적으로 e나 exception을 사용함.

- 예외 객체의 속성

name : 예외 이름, message : 예외 메시지

아래 예제는 배열을 너무 크게 선언해 오류를 발생시키고, 이를 예외 처리하여 오류를 출력함.

<script>
    try {
        const array = new Array(999999999999999)
    } catch (exception) {
        console.log(exception)
        console.log()
        console.log(`예외 이름: ${exception.name}`)
        console.log(`예외 메시지: ${exception.message}`)
    }
</script>

 

2) 예외 강제 발생

예외를 강제로 발생시킬 때는 throw 키워드를 사용함.

//단순하게 예외를 발생시킴
throw 문자열

//조금 더 자세하게 예외를 발생시킴
throw new Error(문자열)

아래 예제에서는 divide() 함수를 만들고 0으로 나눌 때 '0으로는 나눌 수 없습니다'라는 오류를 발생시킴.

<script>
    function divide(a, b) {
        if (b === 0) {
            throw '0으로는 나눌 수 없습니다.'
        }
        return a / b
    }
    console.log(divide(10, 2))
    console.log(divide(10, 0))
</script>

아래 예제에서는 자바스크립트의 undefined와 NaN의 값이 뜰 때 예외를 강제로 발생시킴

<script>
    function test(object) {
        if (object.a !== undefined && object.b !== undefined) {
            console.log(object.a + object.b)
        } else {
            throw new Error("a 속성과 b 속성을 지정하지 않았습니다.")
        }
    }
    test({})
</script>

 

 

Review

벌써 6주의 여정이 끝났다. 길다면 길고, 짧다면 짧았다.

6주 사이에 회사에서도 많은 일이 있었고, 혼공단에서도 많은 일이 있었다.

일단 양쪽에서 많은 걸 배우고 성장했고, 무엇을 개발하고 배우든 내가 아는 것보다 개발의 세상은 훨씬 넓기에, 항상 배우려는 자세로 임하고 그 어느 직업보다도 공부에 느슨해져서는 안되는 이 직업을 더 사랑하게 되었다.

더 많은 이야기는 회고에서.. 아무튼 6주동안 정말 고생 많았다!