DOM APIs

여기서는 웹사이트 구축에 꼭 필요한 html DOM을 다룹니다

자바스크립트는 웹문서에 동적인 움직임을 주기위해 만들어진 프로그래밍 언어로서, 자바스크립트로 작성한 프로그램이 스크립트인데, 브라우저에 내장된 자바스크립트 엔진은 스크립트 코드를 읽어들여서 분석하고(= 파싱), 컴퓨터가 알 수 있는 기계어로 번역하여(= 컴파일), 그 코드 내용을 실행해나가게 된다!

자바스크립트는 많이 어렵습니다. 순서대로만 설명할 수도 없고, 그렇게 배워나갈 수도 없습니다. 지나갔다, 다시 돌아오고,, 다시 나아가고,,, 하면서 수없는 반복을 통해 익혀나가야 합니다 ㅡㅡ; 러시아 혁명가 레닌이 쓴 유명한 팜플렛 [1보 전진 2보 후퇴](= 한걸음 나아가기 위한 2보 후퇴)가 생각나는군요 혹시나 이 말에 의문을 품는 분이 계실까 해서 붙이는데.. [1보 후퇴 for 2보 전진]이 아닙니다 ^^

html DOM

문서 객체모델은 html 문서의 프로그래밍 인터페이스로서, 웹 문서(와 그 내부 요소들)을 자바스크립트 등의 웹프로그래밍 언어로 접근하여 다룰 수 있도록 해준다

돔 트리의 구조
Document 객체는 브라우저가 불러온 웹 문서를 가리키며, 그 내부 컨텐츠들로의 진입점 역할을 한다. DOM 트리는 현재의 웹문서에 포함된 <body> 및 여타 문서 내 모든 컨텐츠들을 계층 구조로 나타내며, 이 Document 인터페이스는 DOM 트리 내부 컨텐츠들의 상태 정보를 얻거나 DOM 트리에 새로운 요소를 생성하는 등의 기능을 전역적으로 제공한다
1. DOM 트리document 객체를 루트로 하여 <html> 아래 <head> & <body>, 그 아래 요소 노드 및 텍스트 노드, 공백 노드 등으로 구성된다. DOM 트리의 각 구성 요소는 Node 객체가 되며, 노드.속성, 노드.메서드()로 접근할 수 있다:

html 문서 전체는 document 객체로 가져온다. 예컨데, document.title은 웹 문서 및 탭의 제목을 가리키는데, 그때 그때 새로운 메시지 등을 알리는데 사용할 수 있다 이 문단에 마우스를 올리고 브라우저 창 제목표시줄의 현재 탭 제목을 보세요..

                                    
                                        
                                    
                                
                                    
                                        const doc_title= document.title // 현재 문서의 제목을 변수로 저장한다

                                        document.querySelector('#title_change').addEventListener('mouseenter', () => document.title='다시 오셨네요 ^^'); // 마우스를 올리면; 문서 타이틀을 변경한다
                                        document.querySelector('#title_change').addEventListener('mouseleave', () => document.title= doc_title); // 마우스를 내리면; 다시 원래 제목을 넣어준다
                                    
                                
2. DOM 트리 안의 Node 객체들에는 html 요소 노드(= Element 객체) 및 그에 딸린 텍스트 노드(html tag 내 텍스트)와 빈 노드(연속된 공백, 탭, 줄 바꿈), 속성 노드(html tag의 속성), 주석 노드 등이 있다:
                                    
                                        
                                    
                                

여기서 <p> 태그 영역 전체는 p 요소 노드가 되며, 그 내부의 class= "para" 부분은 속성 노드, 텍스트 영역 부분은 텍스트 노드가 된다 html 태그의 시작 및 끝 부분 공백은 제외하고, 각 태그 앞/뒤의 연속적인 공백 문자는 '빈 노드'로 생성된다!


노드는 DOM 트리의 계층 구조상 지위만 아니라, 다른 노드를 기준으로 하는 위치로도 참조된다. 우선, Root 는 DOM 트리의 최상위 노드를 말하는데, html 문서에서의 최상위 노드는 언제나 html 노드이다. Child 는 현재 노드 밑 단계에 속한 노드들(= 자식 요소들)을, Parent 는 현재 노드를 포함하고 있는 상위 노드(= 부모 요소)를 가리킨다. 한편, Sibling 은 현재 노드와 동일한 단계에 위치하는 노드들(= 형제 요소들)을, Descendant 는 DOM 트리에서 하위 수준에 포함된 노드들(= 후손 요소들)을 가리킨다


✓   각 요소들은 . 연산자에 의한 체인 연결로 참조할 수 있지만, 요소 노드 사이에 있는 공백 노드의 존재로 인해 다루기에 불편하다. 그에 공백 노드를 제외한 요소만의 참조를 위한 속성도 준비되어 있지만(예컨대, 부모요소.children), 역시 불편하여 실상은 요소의 idclass 를 쓰게 된다!

3. NodeList 객체는 일반적으로 부모요소.children과 같은 속성이나 document.querySelectorAll()과 같은 메서드에 의해 반환되는 정적(및 동적) 노드 컬렉션으로서 유사배열 객체이다. 예컨대, 부모요소.children에서 각 자식 요소들은 for .. of 문이나 forEach()와 같은 메서드로 반복할 수 있고, 배열에서와 같은 방식으로 접근할 수 있다:
                                    
                                        const el= document.querySelector('#my_container')

                                        for (const child of el.children) { // el 내부 모든 자식 요소들로 루프를 돈다 ← children은 (텍스트 노드 등은 제외하고)요소 노드만을 대상으로 한다!
                                            console.log(child.tagName) // el에 포함된 모든 태그명을 출력한다
                                        }
                                    
                                

NodeList 컬렉션은 유사배열 객체이므로 for 문이나 forEach() 메서드로 반복하거나, Array.from() 메서드를 써서 배열로 변환할 수도 있다. 나아가, entries(), values(), keys() 등의 메서드들도 사용할 수 있다!


✓   참고로, document.querySelectorAll()은 호출 시점에서의 정적 노드 리스트를 가져오는 반면, 부모요소.children은 돔의 변경 사항을 실시간으로 반영한다. 따라서, 노드 리스트에서 순회하거나, 그 길이(length)를 가져와야 할 때는 이 점에 유의해야 한다!

돔 이벤트 호출: on 이벤트
이벤트명 앞에 on을 붙여 해당 이벤트를 호출한다: 이벤트 대상.onclick / onwheel / onscroll / onvisibilitychange; 등.. 이벤트 제거는 이벤트 대상.onclick= null;

                                    
                                        
                                    
                                
                                    
                                        let time_pic= document.querySelector('#on_click_button')

                                        function cPic() { // 이벤트 처리함수
                                            let d= new Date()
                                            alert("현재 시각: " + d.toLocaleString())
                                        }
                                    
                                        time_pic.onclick= cPic // Click 이벤트로 cPic() 함수를 호출한다
                                    
                                

이벤트 처리함수 호출 시에는 일반 함수 호출과는 달리 함수명 뒤에 ()를 붙이지 않는다 뒤에 괄호를 붙이게 되면; 이벤트 호출 버튼을 클릭하기 전에 먼저 함수 코드가 실행되어버린다!


✓   참고로, 최근에는 이 on 이벤트보다는 를 쓰는 것이 일반적이다. 타겟.addEventListener()는 같은 리스너에 대해 다수의 핸들러 함수를 등록할 수 있다는 점, 제거 시에도 짝이 되는 removeEventListener()가 존재한다는 점, 같은 이벤트 타입에 대해 손쉽게 복수의 이벤트 핸들러 함수를 등록할 수 있다는 점 등에서 on 이벤트에 비해 보다 유용하다!

돔의 컨텐츠 읽고쓰기
모든 html 요소는 요소.textContent 속성으로 요소 내 (순수)텍스트를 가져오거나 쓸 수 있다. 한편, 요소.innerHTML을 쓰면; (요소 내부에 html 코드가 포함되고, 분석된)텍스트 읽기 및 쓰기가 가능하고, 나아가, 요소.outerHTML을 쓰면; (요소 자신까지 포함하는 html 코드가 포함되고, 분석된)텍스트 읽기 및 쓰기가 가능해진다:

* 경주 남산 날씨정보) 약간 흐림 (현재 23도)

                                    
                                        
                                    
                                
                                    
                                        
                                    
                                

대상요소.insertAdjacentHTML('삽입위치', 'html코드')는 주어진 html코드대상요소를 기준으로 하는 삽입위치 값에 맞추어 삽입한다:

[ 삽입위치 옵션값 ]
                                        
                                            
                                        
                                    

기존에 존재하고 있던 요소의 앞에 배치되면; 기존 요소들은 뒤로 밀려난다!

html 코드 삽입 예)


✓   사용 중인 모든 요소를 없애고 새로 파싱하는 innerHTML()과는 달리, 이미 사용 중인 요소는 제외하고 새로 추가되는 요소에 대해서만 작업한다는 점에서 차이가 있는데, 당연히 돔 갱신 속도도 더 빠르다 참고로, 일반 텍스트는 insertAdjacentText() 메서드를 쓸 수도 있지만, textContent 속성을 쓰는 것이 쉽고 간결하다!

DOM 요소 다루기

DOM에 접근하여 읽고 쓸 수 있게 되었다면; 다음으로는 DOM에 새로운 요소를 생성하고 제거, 삽입하는 방법도 또한 알아야 한다 - 이는 동적 인터페이스 구축에 필수적이다!

돔 요소에 접근하기
DOM의 요소를 다루려면; 먼저 조작하고자 하는 대상요소를 선택하고, 해당 요소에 대한 참조를 변수로 저장해야 한다: <p id="log">조작하려는 대상요소</p> ID가 log 인 <p> 요소
                                    
                                        const log_var= document.querySelector('#log') // 조작하려는 대상요소(#log)에 대한 참조변수 log_var
                                    
                                
1. document.querySelector('선잭자')는 주어진 Css 선택자 ('#id명', '.class명', 'tag명')으로 선택하는데, 처음 만나는 하나의 요소만 반환한다(찾지 못하면; null). 한편, document.querySelectorAll('선택자')는 주어진 선택자 를 지닌 요소 전부를 선택하는데, 찾은 것들은 NodeList 컬렉션으로 반환된다(찾지 못하면; 빈 NodeList)

* 아래서, html 코드로 <p>기존 p 문단</p> 요소를 작성했습니다:

기존 p 문단


** 이어서, 위 <p> 요소에 <p id="log_element_p">기존 p 문단</p>id 를 넣어주고, 다음 스크립트 코드를 작성해주면; 위 <p> 요소 안 텍스트가 아래와 같이 바뀌게 됩니다!

기존 p 문단

                                    
                                        const log= document.querySelector('#log_element_p')
                                        log.textContent= '기존 p 문단의 내용을 변경함!'
                                    
                                
  • 리스트1
  • 값 변경할 리스트2
  • 리스트3
                                                    
                                                        
                                                    
                                                
                                                    
                                                        const listEl= document.querySelector('.log_element_list li:nth-child(2)')
                                                        listEl.textContent= '두번째 li의 값 변경함!'
                                                    
                                                

위에서와 같이, Css 선택자를 조합하여 지정할 수도 있다: '#content div > p', 'p.warning, p.note' 등..

2. document.getElementsById('id명'), getElementsByClassName('class명'), getElementsByTagName('tag명'), getElementsByName('name명')(name= '값';)는 DOM의 요소 노드에만 (그 id명, class명, tag명, name명으로)접근하는데, 반환되는 값은 실시간으로 반영되는 HTMLCollection 객체이므로 forEach() 메서드는 사용할 수 없고, for 문을 써야한다:

* 아래, 리스트들을 각각 클릭해보세요..

  • 리스트1
  • 리스트2
  • 리스트3
                                    
                                        
                                    
                                
                                    
                                        document.querySelectorAll('.ex_list_item').forEach((targetBox) => { // forEach() 사용 가능!
                                            targetBox.addEventListener('click', () => alert(`${targetBox.textContent}`))
                                        })
                                    
                                

** 위 스크립트 코드의 querySelectorAll('.ex_list_item') 대신에 getElementsByClassName('ex_list_item')를 쓰게되면; forEach() 메서드는 사용할 수 없고, 아래와 같이 for 문을 써야하는데, 여기서는 Click 이벤트 또한 사용할 수 없다!

                                    
                                        const box_list= document.getElementsByClassName('ex_list_item')

                                        for (let i=0; i < box_list.length; i++) { // for문 사용 ← forEach()는 사용할 수 없다!
                                            console.log(box_list[i].textContent + " ") // 여기서 클릭 이벤트는 쓸 수 없다!
                                        }
                                    
                                

querySelectorAll()에 의해 반환되는 노드 리스트는 호출 시점에서의 정적 요소들인 반면, getElementByID() 등에서의 노드 리스트는 동적으로 바뀌는 요소들을 반영하므로 리스트를 순회하면서 요소를 추가하거나 리스트의 길이를 알아야 하는 경우 등에는 노드 리스트 객체의 정적 복사본을 만들어 순회해야 한다!


✓   특별한 이유가 없다면; querySlector()querySlectorAll()을 쓰는 것이 알아보기 쉽고, 이점도 많다. 나아가, querySlector('#id')getElementByID('id명')과는 달리, Document 객체만 아니라 Element 객체에서도 사용할 수 있다!

3. 요소.matches('선택자')요소 가 주어진 선택자 에 일치하는 지 여부를 확인하여 true/false 값으로 반환한다:
  • 서울 남산
  • 경주 남산
  • 강릉 남산
                                    
                                        
                                    
                                
  • 서울 남산
  • 경주 남산
  • 강릉 남산
                                    
                                        const namsans= document.querySelectorAll("#ko-namsans li")
                                        const log1000= document.querySelector('.silla1000-log')
                                    
                                        for (const namsan of namsans) {
                                            if (namsan.matches(".silla_1000")) { // silla_1000 클래스를 가진 요소를 찾으면;
                                                log1000.textContent= `${namsan.textContent}은 신라 천년의 보고!` // 해당 요소에 silla1000-log 클래스를 가진 요소의 값으로 넣어준다!
                                            }
                                        }
                                    
                                

노드 생성과 삽입, 제거

DOM에 접근하여 읽고 쓸 수 있게 되었다면; 다음으로는 DOM에 새로운 요소를 생성하고 제거, 삽입하는 방법도 또한 알아야 한다 - 이는 동적 인터페이스 구축에 필수적이다!

노드 생성 및 삽입하기
document.createElement('요소')요소 노드를 생성하며, document.createTextNode('텍스트')텍스트 노드를 생성한다 단, 이 자체로는 노드가 만들어질 뿐, 자동적으로 html DOM에 추가되지는 않는다!
                                    
                                        const article= document.querySelector("article") // article 요소를 참조하는 변수 article

                                        const para= document.createElement("p") // 빈 p 요소를 생성한다
                                        para.textContent= "새로 만든 p 요소에 컨텐츠를 넣어줌" // 만들어진 p 요소에 텍스트를 넣어준다
                                        article.append(para) // 생성된 p 요소(와 텍스트)를 article 내부 맨 끝에 붙인다
                                    
                                

참고로, document.createTextNode('텍스트')텍스트 노드 를 생성하지만, 굳이 이것을 사용해야만 할 이유는 없다 그 이유는 위 코드에서 스스로 확인해보십시오..

1. 부모요소.prepend/append('자식노드')부모요소 내부 맨 앞/뒤에 자식노드 를 (개수 제한없이)삽입한다:

* 아래서, html 코드로 <ol> 리스트를 작성했습니다:

  1. 리스트
  2. 리스트
  3. 리스트
                                    
                                        
                                    
                                

** 이제, 아래 스크립트 코드를 작성해줍니다:

                                    
                                        
                                    
                                

요소 노드만 아니라 텍스트 문자열도 가능한데, 이는 자동적으로 텍스트 노드로 생성된다!

*** <ol> 요소 맨 앞에 들어갈 <li> 요소는 스스로 작성해서 덧붙여주십시오.. 결과는?

  1. 리스트
  2. 리스트
  3. 리스트

✓   부모요소.appendChild('자식노드')자식노드부모요소 내부 맨 끝에 삽입하는데, 역시 현재로서는 굳이 쓸 이유가 없어진 구식이다!

2. 형제요소.before/after()형제요소 앞/뒤에 형제요소(들) 을 (개수 제한없이)삽입하는데, 요소 노드만 아니라 텍스트 문자열도 가능하다:
                                    
                                        
                                    
                                

* 아래서, html 코드로 제목 라인을 작성했습니다:

제 1장
                                    
                                        
                                    
                                

** 이제, 아래 스크립트 코드를 작성해줍니다:

제 1장
                                    
                                        
                                    
                                
                                    
                                        const introduce= "* 이 파트에서는 노드의 생성과 삽입, 삭제에 관해 살펴봅니다"
                                        const heading= document.querySelector("h5.h5_heading")

                                        heading.after(document.createElement('hr'), introduce)
                                    
                                

이전에 살펴본 부모요소.append/prepend()로는 자식 리스트 중간에 요소 노드나 텍스트 노드를 삽입할 수 없다. 그런 이유로, 이와 같이 기준으로 삼을 형제노드의 참조를 가져온 뒤, 그 앞이나 뒤에 삽입하는 것이다 참고로, (요소 노드만 아니라)텍스트 노드를 기준점으로 삼는 것도 가능하다!


✓   참고로, 부모요소.insertBefore(새노드, 기준노드)부모요소 내부의 기준노드 앞에 새노드 를 추가하는데, 요소.before/after()를 사용하는 것이 알기 쉽고, 더 유용하다!

노드 삭제 및 대체하기
요소.remove()요소 자신을 제거하며, 요소.replaceWith(새노드)요소 자신을 새노드 로 대체한다(리턴값은 없다):
                                    
                                        
                                    
                                
                                    
                                        const meBox= document.querySelector('.box_me')

                                        meBox.remove() // meBox 자신을 제거한다
                                    
                                

* 먼저, <span> 요소를 만들고 클래스 값을 넣어줍니다: <span class="box_me1">Me</span>

이어서, 아래 스크립트 코드를 작성해줍니다: 결과는? Me

                                    
                                        const old_pBox= document.querySelector('.box_me1')

                                        const new_pBox= document.createElement('span')
                                        new_pBox.textContent= '새로운 Me'

                                        old_pBox.replaceWith(new_pBox) // old_pBox를 새로 생성한 new_pBox로 대체한다!
                                    
                                

부모요소.removeChild(자식노드)부모요소 내부 자식노드 를 제거하지만, 요소.remove()(로 스스로 요소 자신을 제거할 수 있는데) 굳이 어렵게 쓸 이유가 없다! 또한, 부모요소.replaceChild(새노드, 기존노드)부모요소 내부 기존노드를 제거하고 새노드로 대체하지만, 이 또한 요소.replaceWith(새노드)(로 스스로 요소 자신을 대체할 수 있는데) 굳이 어렵게 사용할 이유는 없다 다만, 이전에 작성된 코드들에서는 자주 나오기에 코드 읽기를 위해 부가했습니다 ㅡㅡ;


✓   참고로, 요소 내부에 자식노드 가 존재하는지 여부는 요소.hasChildNodes()로 확인할 수 있다!

노드 이동 및 복제

노드 이동 및 복제하기
1. html 요소는 문서의 한곳에서만 존재할 수 있다. 곧, 기존의 요소를 새 위치에 삽입하면; 그 위치로 '이동'할 뿐 '복사'되는 것은 아니다!

* 먼저, 복사하려는 텍스트 문단을 만들고, 이를 복사해서 넣어줄 <div> 박스를 만든다:

(복사하려는 텍스트)

복사해서 넣어줄 div 박스
                                    
                                        
                                    
                                
                                    
                                        const introduce_1= document.querySelector('p.inter_p1')
                                        const heading_1= document.querySelector(".heading5")
                                    
                                        // heading_1 다음에 introduce_2를 복사해서 넣으려는 의도였지만..
                                        heading_1.after(introduce_1) // 기존에 존재하던 요소이기에, 이동이 된다!
                                    
                                

이 스크립트를 적용한 이후의 결과는? 아래를 보십시오:

(복사하려는 텍스트)

복사해서 넣어줄 div 박스

요 위에 복사하려던 의도였지만.. 실제로는 이동이 된다 ← 곧, 이 박스 바깥의 기존 <p> 요소는 사라졌다!

2. 요소.cloneNode([true])는 현재 문서 내에 있는 요소를 복사한다. 기본적으로는 요소만 복사하는데, 인수로 true 를 주면; 내부 텍스트 및 자식 요소들까지 전부 복사한다:

* 아래, <span> 요소(전부)를 복사해서 <p> 요소 박스 안에 붙여넣고자 한다:

                                    
                                        
                                    
                                
                                    
                                        
                                    
                                

이 스크립트를 적용한 이후의 결과는? 아래를 보십시오:

붙여넣을 요소

붙여넣을 곳:


다른 문서에 있는 노드를 복사해올 때는; document.importNode(가져올 노드[, true]) 메서드를 사용한다: var clone_node= document.importNode(가져올 노드, true); true 옵션을 주면; 하위 자식요소들까지 모두 포함한다

DOM의 문서조각

DocumentFragment는 부모노드를 갖지 않는 문서객체로서, 여기에는 일반 문서처럼 노드로 구성된 하위 문서 구조를 저장할 수 있으므로 Document 객체의 가벼운 버전으로 사용된다!

돔에 문서조각 생성 및 삽입하기
DocumentFragment 객체DocumentFragment() 생성자나 document.createDocumentFragment() 메서드를 써서 만드는데, 일반적인 용도는 자신이 임시 부모요소가 되어 appendChild()insertBefore() 등의 메서드를 써서 돔에 하위 트리를 삽입하는 것이다:
  • 임시 <ul> 및 <li>
                                    
                                        
                                    
                                

* 이제, 아래 스크립트 코드를 작성하여 넣어주면; 생성되는 DocumentFragment의 자식노드들은 모두 (한번에)DOM으로 이동하고, 스크립트에는 빈 DocumentFragment만 남게된다!

                                    
                                        const list= document.querySelector("#fruits_list")
                                        const fruits= ["배", "사과", "감귤"]

                                        const fragment= new DocumentFragment()

                                        fruits.forEach((fruit) => {
                                            const li= document.createElement("li")
                                            li.textContent= fruit
                                            fragment.appendChild(li)
                                        });

                                        list.appendChild(fragment)
                                    
                                

이 스크립트를 적용한 이후의 결과는? 아래를 보십시오:


    ✓   DocumentFragment는 일반 요소노드처럼 사용할 수 있지만, 활성화된 문서 트리구조의 일부가 아니므로 내부 트리를 변경해도 문서에 아무런 영향을 주지 않으며, 문서에 삽입될 때는 (자신이 아니라!)자식요소들이 (모두 일거에!)돔으로 이동하기에 리플로우도 발생하지 않아 성능상 이점이 크다!

    Style 및 Link 태그
    <style>, <link>, <script> 또한 html 태그이므로 id 속성을 쓸 수 있고, document.querySelector()로 불러올 수 있다! <style>, <link> 태그의 Element 객체에는 disabled 속성이 있고, 이를 이용하면 스타일시트 전체를 비활성화할 수 있고, Link 태그의 생성 및 삽입도 가능하다:
                                        
                                            function setTheme(name) { /* 스타일시트에 Link 생성 및 삽입하는 함수 */
                                                // <link href="themes/name.css" rel='stylesheet'>를 생성하여 스타일시트 파일을 불러온다
                                                let link= document.createElement('link')
                                                link.id= 'theme'
                                                link.rel= 'stylesheet'
                                                link.href= `themes/${name}.css`
    
                                                // id가 [theme]인 기존 링크를 찾아 교체 또는 삽입한다
                                                let currentTheme= document.querySelector('#theme')
                                                if (currentTheme) currentTheme.replaceWith(link) // 링크 교체
                                                else document.head.append(link) // 링크 삽입
                                            }
                                        
                                    
    Script 태그
    1. <script> 태그의 type 속성에 text/x 와 같이 임의의 값만 지정해주면(x 에는 유효하되, 자바스크립트가 아닌 임의의 MIME 타입이 들어가기만하면 된다!); 내부 컨텐츠는 스크립트 코드로 실행되지 않는다. 곧, 내부 컨텐츠는 브라우저가 처리하지 않을 데이터 블록으로 간주된다 그럼에도 문서 트리에는 남아 있으며, text 속성으로 그 데이터를 가져올 수 있다!
    [ 스크립트에 사용자 데이터 보관 및 가져오기 ]
                                            
                                                <script id="my-data" type="text/x">
                                                    "userId": "jcunicom", "userName": "Kjc"
                                                </script>
                                            
                                        
                                            
                                                const me_data= document.querySelector("#my-data").text
    
                                                console.log("사용자 정보:", me_data); // 사용자 정보: "userId": "jcunicom", "userName": "Kjc"
                                            
                                        

    ✓   src 속성이 없는 인라인 <script> 요소에는 text 속성이 있고, 그 안의 컨텐츠는 절대 브라우저에 표시되지 않는다(꺽쇠& 기호 또한 html 파서에 의해 분석되지 않고 그대로 표시된다). 이러한 특징은 <script> 요소를 애플리케이션에 사용할 임의의 텍스트 데이터를 안전하게 보관해둘 장소로 활용할 수 있도록 해준다!

    2. 나아가, <script> 요소와 JavaScript의 유효한 MIME 타입을 사용하면; 서버에 보관된 데이터를 서버사이드 렌더링을 통해 웹문서로 가져올 수도 있다:
    [ 웹서버에 보관된 사용자 데이터 가져오기 ]
                                            
                                                <!-- 웹서버에 보관된 데이터 -->
                                                <script id="data" type="application/json">
                                                    { "userId": "jcunicom", "userName": "Kjc" }
                                                </script>
                                            
                                        
                                            
                                                const user_info= JSON.parse(document.querySelector("#data").text)
    
                                                console.log("사용자 정보:", user_info) // 사용자 정보: { userId: 'jcunicom', userName: 'Kjc' }
                                            
                                        

    Web 컴포넌트

    Web 컴포넌트는 그 기능을 나머지 코드로부터 캡슐화하여 재사용 가능한 커스텀 엘러먼트를 생성하고, 웹 앱에서 활용할 수 있도록 해주는 다양한 기술들의 모음이다

    웹 컴포넌트 사용하기
    Web 컴포넌트 모듈 불러오기 및 사용하기:
    [ Web 컴포넌트 모듈 불러오기 및 사용하기 ]
                                            
                                                <script type="module" src="components/search-box.js"></script>
                                            
                                        
                                            
                                                
                                            
                                        

    Web 컴포넌트이름 에는 반드시 -이 들어가야 하며, slot 속성은 자식요소를 어디에 표시할지를 지정하는데, 슬롯 이름(위 코드의 left, right)은 Web 컴포넌트에서 정의한다


    ✓   웹 브라우저는 컴포넌트가 정의되기 전에 웹 컴포넌트를 만날 경우, 범용 HTMLElement 노드를 DOM 트리에 추가해둔 뒤, 나중에 커스텀 엘러먼트가 정의되면; 이를 업그레이드하게 된다. 만약, 웹 컴포넌트에 자식요소가 존재한다면; 자식요소는 컴포넌트가 정의되기 전에는 부정확하게 표시되는데, 이 경우, Css를 써서 숨겨줄 수 있다:

                                        
                                            search-box:not(:defined){
                                                display: inline-block; width: 300px; height: 50px; opacity: 0;
                                            }
                                        
                                    

    <search-box> 컴포넌트가 정의되기 전에는 숨기되, 위와 같이 최종 레이아웃과 크기를 미리 확보해두면; 요소가 생성되면서 주변 컨텐츠를 다시 렌더링해 성능이 떨어지는 일을 막을 수 있다!

    1. 웹 컴포넌트의 중요한 측면은 캡슐화로서, 이 캡슐화를 통해 마크업 구조, 스타일, 동작을 숨기고 페이지의 다른 코드로부터의 분리하여 각기 다른 부분들이 충돌하지 않게 하고, 코드가 깔끔하게 유지될 수 있도록 한다. 새도우 돔 API는 캡슐화의 핵심 파트이며, 숨겨진 분리된 돔을 요소에 부착하는 방법을 제공해준다!
    2. <template>과 <slot> 요소는 렌더링된 페이지에 나타나지 않는 마크업 템플릿을 작성할 수 있게 해준다. 그런 이후, 커스텀 엘러먼트 구조를 기반으로 여러번 재사용할 수 있다 html의 <template> 요소는 페이지를 불러온 순간 바로 그려지지는 않지만, 이후 스크립트를 써서 인스턴스를 생성할 수 있는 html 코드를 담을 방법을 제공한다!

    웹 컴포넌트를 직접 작성하는 것은 어렵습니다. 그런 연유로 두서없이 간략한 소개만 했습니다. 나중에라도 필요해지시면; 웹 컴포넌트 작성을 도와주는 라이브러리를 써보십시오..

    요소의 속성 다루기

    CSSStyleSheet 객체는 Css의 다양한 스타일에 대응하는 속성을 가지고 있으며, 이 값을 바꾸면 동적으로 요소의 스타일을 바꿀 수 있다!

    요소의 속성 다루기
    1. 요소.getAttribute("속성")요소속성 값을 가져오며, 요소.setAttribute("속성", "값")요소의 (없다면; 새로 추가하여) 속성 을 넣는다. 하지만 html 요소의 속성은 모두 그 요소를 나타내는 HTMLElement 객체의 속성으로 존재하므로, 그냥 스크립트 속성을 쓰는 편이 알기쉽다: 요소.속성(요소속성 값 가져오기) 및 요소.속성= "값"(요소속성 넣기)
                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    
    스크립트의 속성명 표기법

    우선, html 요소의 속성명과는 달리, Css 및 JavaScript의 속성명에서는 대/소문자를 구분한다. 다음, 속성명을 표기할 때 Css에서는 kebab-case 표기법(예컨대, background-color)을 쓰지만, JavaScript에서 사용할 때는 camelCase 표기법으로 바꿔줘야 한다:

    • 작명 규칙의 기본:
      • 모두 소문자로 작성하되, 단어를 연결하는 -은 없애고, 다음 단어의 시작 글자는 대문자로 작성한다: backgroundColor
    • 예외적인 작명 규칙들:
      • 스크립트의 키워드 는 앞에 html을 붙여준다: htmlFor <label> 요소의 for 속성
      • float 는 특별히 앞에 css를 붙여준다: cssFloat
      • class 는 특별히 뒤에 Name을 붙여준다: className
      • webkit 앞에 붙는 --는 제거해준다: webkitTransform
      • 이벤트 핸들러의 프로퍼티 이름은 그대로 사용한다: onclick
    2. 요소.createAttribute("속성")요소속성 을 생성하며, 요소.removeAttribute("속성")요소속성 을 제거한다. 요소.setAttributeNode("속성노드")요소속성노드 를 연결한다:

                                        
                                            <button id="new_para"> 클릭 - 목없는 부처님 </button>
                                        
                                    
                                        
                                            let txt_para= document.querySelector('#new_para')
    
                                            txt_para.addEventListener('click', function() { // 클릭 이벤트가 발생하면;
                                                const attr= document.createAttribute('class') // 'class' 속성을 생성하고,
                                                attr.value= 'ex_box rounded' // 그 속성에 값을 넣는다
                                                new_para.setAttributeNode(attr) // 만든 attr 속성(과 그 값)을 new_para 요소에 넣어준다
    
                                                const img_new= document.createElement('img') // img 요소를 만들어,
                                                img_new.setAttribute('src', '_images/목없는부처님-small.jpg') // src 속성을 만들고, 값을 넣어준다
                                                new_para.append(img_new) // new_para 내부 맨 뒤에 삽입한다
                                            });
                                        
                                    

    ✓   참고로, 요소에 특정 속성 이 존재하는지 여부는 요소.hasAttribute("속성") 메서드로 확인할 수 있다!

    3. 모든 요소 객체에는 attributes 프로퍼티가 정의되어 있는데, 요소.attributes는 주어진 요소의 속성 컬렉션을 NamedNodeMap 객체로 반환한다:
    [ NamedNodeMap 객체 ]
                                            
                                                
                                            
                                        
                                            
                                                const para= document.querySelectorAll("div p")[0]
    
                                                const attrs= para.attributes // para의 모든 속성들을 NamedNodeMap 객체로 가져온다
                                                console.log(attrs) // NamedNodeMap {0: class, 1: style, length: 2}
                                            
                                        

    NamedNodeMap 객체는 요소 객체가 지닌 속성과 그 값이 담긴 읽기전용 유사배열 객체로서, 요소.attributes.속성명.value로 그 에 접근할 수 있다:

                                        
                                            <button type="button" value="클릭!">버튼</button>
                                        
                                    
                                        
                                            const btn= document.querySelector('button')
    
                                            console.log(btn.attributes.type.value, btn.attributes.value.value) // button 클릭!
                                        
                                    
    대괄호 구문으로 요소의 속성 찾기
    요소의 속성을 찾을 때, [] 문법을 쓰면 어떤 속성으로든 찾을 수 있는데, 아래 코드는 html 5의 data- 속성 값으로 찾고 있다:
                                        
                                            
                                        
                                    
                                        
                                            const attr= document.querySelector('[data-my-name]')
    
                                            // DOMStringMap은 dataset 속성을 통해 접근할 수 있다:
                                            attr.dataset // DOMStringMap {myName: 'Kjc'}
    
                                            // data- 속성값 가져오기 1. 'data-' 뒤의 속성 이름 부분을 사용해 속성값을 가져온다
                                            attr.dataset.myName // Kjc ← 이름은 카멜케이스 방식으로 변환하여야 한다!
    
                                            // data- 속성값 가져오기 2. 완전한 html 속성명과 getAttribute()를 사용해 속성값을 가져온다
                                            attr.getAttribute('data-my-name') // Kjc
                                        
                                    
    ➥ html의 data-이름 속성

    html 태그에 data-이름= "값" 속성(이름 에 대문자는 들어갈 수 없다!)을 사용하면; 해당 태그에 특정 정보를 추가할 수 있는데, 이 정보는 Css 및 JavaScript에서 읽어들여서 다룰 수 있다!

    html 요소의 data-이름 속성은 사용자 지정 데이터 속성 클래스를 형성함으로써 임의의 데이터를 스크립트로 html과 DOM 사이에서 교환할 수 있도록 하는데, dataset 프로퍼티를 사용하여(요소.dataset.이름) 이 속성에 접근할 수 있다. 또한, 이 data-이름 속성은 평문 html 코드이기에 Css에서도 접근 및 조작할 수 있고, 나아가 html 요소에 임의의 사용자 지정 데이터를 추가하는 것도 가능하다!

    Class 속성 다루기

    모든 요소 객체에는 DOMTokenList 객체classList 속성 또한 마련되어 있는데, 이는 유사배열 객체로서 HTMLCollection 객체와 마찬가지로 살아 있는 상태를 반영한다!

    클래스 속성 다루기
    1. 요소.className 속성은 요소class 속성값(또는, class 속성값들이 공백으로 분리 나열된 문자열)을 읽어오거나 설정한다 기존 요소에 class 속성이 없다면; 새로 생성하면서(class=) 을 넣어준다!

    여기에 마우스를 올리세요..

                                        
                                            
                                        
                                    
                                        
                                            .change_color_red { color: red }
                                            .change_color_green { color: green }
                                        
                                    
                                        
                                            
                                        
                                    

    기존에 설정되어 있던 속성들은 완전히 비워지고(위 html 코드의 기존 클래스값들은 다 제거된다!) 새로이 추가하는 값만 들어간다!

    2. 요소.classList.add/remove('클래스'[, '클래스2', ..]) 요소classList클래스 를 추가/제거한다 존재하지 않는 클래스를 제거하려고 시도해도 에러는 발생시키지 않고, 그저 '가만히' 실패할 뿐이다!
    [ classList 사용하기 ]
                                            
                                                
                                            
                                        

                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    

    classList 속성은 현재의 살아 있는 상태에 반응한다!

    3. 요소.classList.replace('기존클래스', '새클래스')요소기존클래스새클래스 로 대체하며, 요소.classList.toggle('클래스')요소classList에 해당 클래스 가 없으면; 추가하고, 있으면; 제거한다

    ✓   요소classList에 특정 클래스 가 있는지 여부는 요소.classList.contains('클래스') 메서드로 확인할 수 있다!

    Style 속성 다루기

    모든 Element 객체에는 인라인 스타일 속성에 대응하는 style 프로퍼티가 존재하는데, 이는 문자열이 아니라 CSSStyleDeclaration 객체이다!

    인라인 Style 다루기
    1. CSSStyleDeclaration 객체style 프로퍼티를 써서 요소의 인라인 스타일을 다룰 수 있다. 요소.style.속성은 요소의 style 속성 값을 가져오고, 요소.style.속성= "값"은 요소의 style 속성에 을 넣는다:
                                        
                                            
                                        
                                    
                                        
                                            const pic_style= document.querySelector('#color_change')
                                            const ele_style= document.getElementById("box-style_ex")
                                        
                                            pic_style.onclick= () => ele_style.style.backgroundColor= ''; // 인라인 스타일 속성(배경색) 설정
                                        
                                    
    2. 또는, getAttribute()setAttribute() 메서드를 이용하거나, CSSStyleDeclaration 객체cssText 프로퍼티를 사용할 수도 있다:
                                        
                                            // [요소1]의 인라인 스타일을 [요소2]의 인라인 스타일에 복사하기:
                                            요소2.setAttribute("style", 요소1.getAttribute("style"))
    
                                            // CSSStyleDeclaration 객체의 cssText 속성 사용하기:
                                            요소2.style.cssText= 요소1.style.cssText
                                        
                                    
    스타일 속성값 표기법

    속성값은 반드시 따옴표로 묶어주어야 하며, 숫자 또한 따옴표로 묶고 단위도 반드시 적어주어야 한다:

                                                
                                                    function tp_pos(tooltip, x, y) { // 툴팁 표시 및 위치(x, y) 설정 함수
                                                        tooltip.style.display= "block" // 따옴표로 묶어주어야 한다!
                                                        tooltip.style.position= "absolute"
                                                        tooltip.style.top= `${y}px` // 숫자도 따옴표로 묶고, 단위도 반드시 써주어야 한다!
                                                        tooltip.style.left= `${x}px`
                                                    }
                                                
                                            

    style 속성으로는 웹 브라우저에 의해 계산된 스타일 값을 확인할 수 없는 경우가 있는데, window 객체getComputedStyle(요소[, '가상요소']) 메서드를 쓰면; 인자로 전달받은 요소의 모든 Css 속성값을 담은, 요소의 스타일이 변경 될 때마다 실시간으로 업데이트되는 살아있는 상태의 CSSStyleDeclaration 객체를 가져올 수 있다:

    이 박스의 가로/세로 크기 입니다

                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    

    ✓   인라인 스타일로서의 CSSStyleDeclaration 객체와는 달리, 계산된 스타일로서의 CSSStyleDeclaration 객체는 읽기 전용이며, % 등의 크기 단위는 모두 px 로 변경되고, 색상값은 모두 rgb(a) 형식으로 표시된다. padding 같은 단축 속성 또한 각각의 방향별로 표시된다 그외에도 많은 특이한 차이점들이 있어 실제로 사용하기에는 좀 혼란스러우니 참고하십시오 ㅡㅡ;

    이벤트 핸들러

    스크립트는 html 문서에 적용될 때, 사용자와의 상호작용을 제공하는데.. JavaScript의 가장 일반적인 용도는 웹브라우저의 DOM을 통해 html과 Css를 동적으로 변경하는 것이다!

    이벤트 리스너
    모든 html 요소에는 addEventListener()라는 이벤트 호출 메서드가 내장되어 있고, 이 메서드를 통해 이벤트가 일어났을 때 호출할 함수를 지정할 수 있다: 타겟.addEventListener(이벤트 타입, 이벤트 핸들러);

                                        
                                            
                                        
                                    
                                        
                                            let txt= document.querySelector('.click_me_color') // 이벤트 대상
    
                                            txt.addEventListener('click', function() { // 클릭 이벤트가 발생하면;
                                                txt.style.color= 'red' // txt의 글자색을 'red' 색으로 변경한다 
                                            });
    
                                            txt.addEventListener('click', function(e) { // 이벤트 객체 e를 매개변수로 전달하여 처리한다
                                                e.currentTarget.style.color= 'red' // e.currentTarget ← 발생한 이벤트가 등록된 DOM 요소
                                            });
    
                                            function setStyle() {
                                                this.style.color= 'red' // 이 this는 자신을 호출한 txt의 this임!
                                            }
    
                                            txt.addEventListener('click', setStyle); // 이벤트를 처리할 함수를 호출한다
                                        
                                    

    위 3가지 방식 모두 같은 결과를 가져온다!

    1. 이벤트 핸들러 함수는 해당 이벤트에 관한 정보가 들어있는 이벤트 타입의 객체 하나만을 매개변수로 받는데(그 이름은 무엇이든 상관없다), 이 이벤트 객체는 발생한 이벤트 관련 다양한 정보가 저장된 프로퍼티와 이벤트 흐름을 제어하는 메서드들을 담고 있다:
                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    
    2. 이벤트 리스너이벤트 객체 외의 추가 정보를 넘기려면; 이벤트 리스너 안에서 이벤트 객체와 추가 정보를 인수로 전달하면서 함수를 호출하면 된다:

                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    

    * 박스 안 여기 저기 클릭해보세요..

                                                        
                                                            
                                                        
                                                    
                                                        
                                                            #div_tile_container {
                                                                height: 135px; width: 100%;
                                                            } .div_tile_color {
                                                                height: 95px; width: 20%; float: left;
                                                            }
                                                        
                                                    
                                                        
                                                            function random_num() {
                                                                return Math.floor(Math.random()*256); // 0 ~ 255 사이의 정수
                                                            }
    
                                                            function backgroundColor() {
                                                                const randomColor= `rgb(${random_num()}, ${random_num()}, ${random_num()})`
                                                                return randomColor;
                                                            }
    
                                                            const container_bgc= document.querySelector('#div_tile_container')
                                                            container_bgc.addEventListener('click', e => e.target.style.backgroundColor= backgroundColor())
                                                        
                                                    
    3. 이벤트대상.removeEventListener(이벤트명, 이벤트 리스너)는 기존에 등록된 이벤트를 그 함수의 참조를 저장한 인스턴스 변수를 사용하여 제거한다 모든 인수는 addEventListener()와 같아야 하고, 화살표 함수는 사용할 수 없다!

    * 버튼을 클릭하여 메시지창을 닫은 뒤, 곧바로 다시 클릭해보고.. 마지막 버튼 클릭 이후 10초가 지난 이후에 다시 클릭해보세요..

                                        
                                            
                                        
                                    
    AbortSignal로 이벤트 제거하기

    1. 익명함수로 정의된 이벤트 리스너는 그 내부에서 스스로 삭제하는 방법을 써야 하는데, 이때는 arguments.callee를 인자로 넘겨서 removeEventListener()를 호출하면 된다:

                                                
                                                    const btn= document.querySelector('.btn_click_remove')
    
                                                    btn.addEventListener('click', function(e) { // 익명함수 이벤트 리스너
                                                        alert("버튼 클릭!")
                                                        e.removeEventListener('click', arguments.callee) // 이벤트 객체 자신(arguments.callee)을 호출하여 이벤트를 제거한다!
                                                    });
                                                
                                            

    하지만, arguments.callee에는 많은 문제점들이 있고, 따라서 권장되지는 않는다 (* MDN의 arguments.callee 참조 )

    2. (* 아직은 예전 브라우저와의 호환성을 고려해야 하는 실험적인 기능이지만) AbortController 객체AbortSignal을 써서 이벤트를 제거해주는 방식도 있다: 일단, AbortSignaladdEventListener()에 전달해두고.. fetch()와 같은 DOM 요청과 통신한 이후, 나중에 필요한 경우, AbortSignal을 소유하고 있는 AbortController 객체에서 abort() 메서드를 호출함으로써 설정된 이벤트를 제거해줄 수 있다 (* MDN의 AbortSignal 참조 )

    [ AbortSignal 사용 예 ]
                                                    
                                                        
                                                    
                                                
                                                    
                                                        const controller= new AbortController()
                                                        let btn= document.querySelector('.btn_click_rem')
                                                        
                                                        function random_num(number) {
                                                            return Math.floor(Math.random()*number);
                                                        }
                                                        
                                                        btn.addEventListener("click", function() {
                                                            const randomColor= `rgb(${random_num(255)}, ${random_num(255)}, ${random_num(255)})`
                                                            btn_click_cont.style.backgroundColor= randomColor
                                                        }, { signal: controller.signal }); // 이 이벤트 핸들러에 AbortSignal을 전달한다
                                                        
                                                        // .. 필요한 작업들 ← 중간에라도 원하던 일이 끝나면; 아래 코드로 점프한다 
                                                        
                                                        controller.abort() // 이 컨트롤러와 연관된 모든 이벤트 핸들러를 제거한다
                                                    
                                                
    디퐅트 이벤트 차단하기
    돔의 이벤트 모델은 이벤트 하나에 다수의 핸들러를 연결할 수 있도록 설계되어 있는데, 예컨대 링크나 이미지 요소, 폼 버튼 등의 기본 설정 핸들러를 막기 위해서는 이벤트 리스너 안에서 preventDefault()를 사용해주면 된다:
                                        
                                            document.addEventListener('DOMContentLoaded', () => {
                                                const imgs= document.querySelectorAll('img')
                                            
                                                imgs.forEach((img) => {
                                                    img.addEventListener('contextmenu', (e) => { // 이미지에서 마우스 우측 버튼이 클릭되면;
                                                        e.preventDefault() // 우측 버튼의 기본 기능인 단축메뉴 기능을 제거한다!
                                                        alert("마우스 우측버튼 사용 불가!")
                                                    });
                                                });
                                            });
                                        
                                    

    preventDefault()는 현재 이벤트가 발생한 요소의 기본 이벤트 동작을 중지한다 - 단, defaultPrevented 속성값만 true 로 바뀐 채 이벤트 전파는 멈추지 않는다!

    갈 수 있게 해주세요.. 소소한 이야기 로 가기

                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    
    이벤트 리스너의 옵션객체

    document.addEventListener('이벤트 이름', 핸들러 함수[, 옵션객체])의 3번째 매개변수인 옵션객체 는(더 많은 세밀한 옵션들이 필요해짐에 의해) 최근에 추가된 것이다:

    [ 이벤트 리스너의 옵션객체 ]
                                                    
                                                        document.addEventListener('이벤트 이름', 핸들러 함수, { // 옵션객체
                                                            capture: true, // 캡쳐링(기본값: false)
                                                            once: true, // 이벤트 한번 호출 후 자동 제거(기본값: false)
                                                            passive: true, // 이벤트 기본 동작 취소 불가(기본값: false)
                                                            signal // AbortSignal 연결 ← 지정한 AbortSignal 객체의 abort() 메서드를 호출하면; 이 옵션은 자동 제거된다!
                                                        });
                                                    
                                                

    capture 옵션은 이벤트가 전파되는 방향을 설정한다. 기본적으로는 돔의 아래서 위로 전파되는데(= 버블링), true 값을 주면; 그 반대 방향으로 전파된다(= 캡쳐링)


    참고로, passive 속성값을 true 로 주면; wheel, mousewheel, touchstart나 touchmove 이벤트가 작동 중일 때 브라우저의 기본 스크롤 동작이 멈추지 않도록 안전핀 역할을 한다(크롬 등 거의 대부분의 브라우저들에서 이 값을 true 로 설정하고 있다!) 따라서, 이런 이벤트에서 preventDefault()를 호출하려 할 때는; 이 값을 명시적으로 false 로 설정해야 한다!

    이벤트 모델

    이벤트가 발생하면; Window 객체에서 시작하여 DOM 트리를 거쳐 이벤트가 발생한 요소까지 내려오며(= 캡처링), 이벤트 실행 후 다시 DOM 트리를 거쳐 Window 객체까지 반대로 거슬러 올라가게 된다(= 버블링)

    이벤트 전파: 버블링 대 캡쳐링
    1. 기본적으로는 타깃에서 돔 트리의 위로 올라가면서 이벤트가 발생되지만, addEventListener()의 세번째 인자로 true 값을 주면; 돔 트리의 맨 위에서부터 타겟으로 내려오면서 이벤트가 발생된다:

    * 아래, 각 영역별로 한번씩 클릭해보세요..

    바깥 div 박스

    내부 p 박스 span

                                        
                                            
                                        
                                    
                                        
                                            const outbox_b= document.getElementById('outbox_bubble')
                                            const inbox_b= document.getElementById('inbox_bubble')
                                            const spanbox_b= document.getElementById('span_bubble')
                                        
                                            const area_box= [outbox_b, inbox_b, spanbox_b]
                                            area_box.forEach(box => {
                                                box.addEventListener('click', () => {
                                                    alert(`${box.tagName}`)
                                                }, false); // 버블링: 기본값 false
                                            });
                                        
                                    

    위 스크립트에서 false (버블링: 기본값) 대신 true (캡쳐링) 값을 주어 버블링캡쳐링의 차이를 확인해보십시오..


    ✓   3번째 인자로 true/false 값을 주는 것은 최근의 옵션객체 옵션 이전에 사용되던 방식인데, 여전히 (구버전 브라우저까지 포함하여, 공히)지원되는 '구식' 방식입니다!

    2. 예컨대, <ul> 및 <li> 같은 관계에서, 이벤트 버블링을 써서 상위 요소로 이벤트를 위임할 수도 있는데, 이 경우 상위 요소에서 이벤트를 받아 제어할 수 있게 된다 단, 내부 각 하위 요소를 제어할 수는 없다!

    자유게시판

    1. 게시글
    2. 게시글
    3. 게시글
    4. 게시글
    5. ..
                                        
                                            
                                        
                                    
                                        
                                            const ul_lists= document.getElementById('ol_list_bubble')
    
                                            ul_lists.addEventListener('click', (e) => {
                                                alert("게시판 클릭함!")
                                                console.log(e.target.id) // gesi_id_1 ← 이벤트가 처음 발생했던 대상 DOM 요소(= li), ol로까지 버블링한다!
                                                // console.log(e.currentTarget.id) // ol_list_bubble ← 발생한 이벤트가 등록된 DOM 요소(= ol)
                                            });        
                                        
                                    

    이렇게 상위 요소 <ol>로 이벤트를 위임하면; <ol> 아래 각 <li>를 클릭할 때 이벤트 버블링을 통해 상위 요소인 <ol>에서 이벤트를 받아 제어할 수 있게 된다 단, 이 경우 각 <li>별로 제어할 수는 없다!


    이벤트 리스너는 html 요소를 포함한 모든 DOM 요소에 이벤트를 등록할 수 있을 뿐 아니라 여타 메서드를 활용하여 더욱 정밀하게 이벤트 전파를 제어할 수 있다. 예컨대, stopPropagation() 메서드는 현재 요소에서 이벤트를 끝내고 다음 단계로 이벤트가 전파되는 것을 막는다(단, 그 이벤트에 연결된 다른 이벤트 리스너들은 변함없이 동작한다). 한편, stopImmediatePropagation() 메서드는 모든 이벤트 핸들러를 즉각 정지시킨다!

    이벤트 리스너에서의 this
    이벤트 리스너 안에서의 this 는 기본적으로 이벤트가 발생한 요소 객체를 가리키는데, 아래는 this를 잘못 사용한 경우의 한 예이다:
                                        
                                            <button id="btn_this">this</button>
                                        
                                    
                                        
                                            function Person(name) {
                                                this.name= name 
                                                this.sayHello= function() {
                                                    alert("hello " + this.name) // hello
                                                }
                                            }
                                            const per= new Person('Kjc')
    
                                            const btn= document.getElementById('btn_this')
                                            btn.addEventListener('click', per.sayHello) // hello !
                                        
                                    

    호출받는 Person() 함수 내부 this 는 이벤트가 발생한 btnthis 가 된다!

    1. 이 문제는, 우선은 bind(per)this 의 주체를 per 로 지정해줌으로써 해결할 수 있다:
                                        
                                            // 방법 1: bind() 메서드로 this의 주체를 묶어주기
                                            btn.addEventListener('click', per.sayHello.bind(per)) // hello Kjc
                                        
                                    
    2. 다음, this가 원하는 객체를 가리키도록 하기 위하여 이벤트객체를 인자로 하는 익명함수 및 화살표함수를 쓸 수도 있다:
                                        
                                            
                                        
                                    
    3. 마지막으로, 객체 생성자 함수 안에서 화살표함수로 메서드를 정의해줄 수도 있다:
                                        
                                            
                                        
                                    
    -> 0
                                                        
                                                            
                                                        
                                                    
                                                        
                                                            class Counter_click {
                                                                constructor() {
                                                                    this.clickedCount= 0 // 버튼 클릭 횟수
    
                                                                    const btn= document.querySelector('.cli-button')
                                                                    const clickedCountText= document.querySelector('.clickedCountText')
    
                                                                    // btn.addEventListener('click', function() { // 일반 함수로 이벤트 리스너 정의
                                                                    //     this.clickedCount += 1
                                                                    //     clickedCountText.textContent= this.clickedCount // NaN ← 이 this는 이벤트 대상인 btn의 this이다!
                                                                    // });
    
                                                                    btn.addEventListener('click', () => { // 화살표 함수로 이벤트 리스너 정의
                                                                        this.clickedCount += 1 // 여기서 this는 Counter_click 클래스 내부를 참조한다!
                                                                        clickedCountText.textContent= this.clickedCount // 여기서 this는 Counter_click 클래스 내부를 참조한다!
                                                                    });
                                                                }
                                                            }
    
                                                            new Counter_click()
                                                        
                                                    

    커스텀 이벤트

    객체간 통신수단에는 콜백 함수를 인수로 넘기는 방식만 아니라 커스텀 이벤트를 보내는 방식도 있다 - 곧, 한쪽 객체에서 이벤트를 발생시키고, 다른 객체에서 그 이벤트를 처리하는 이벤트 리스너를 등록해서 처리하는 방식이다. 이런 방식은 DOM이 제공하는 표준적인 방법을 사용하여 손쉽게 객체간 통신을 가능하게 한다는 점에서 의의가 있다!

    커스텀 이벤트 만들기
    먼저, const evt= new Event(이벤트이름[, {옵션 객체}])이벤트 객체를 생성하고(옵션은 {bubbles: false/true, cancelable: false/true}), 타겟.dispatchEvent(evt)로 타겟 요소에 이벤트를 보내면 동기적으로(순서대로) 즉시 실행된다:

    * 먼저, 이벤트를 전달받을 타깃 요소를 만들어준다:

    이벤트를 전달받을 타깃 요소

    ** 다음, 이벤트를 전달받을 타깃 요소에 id 를 넣어주고(id="target-ele"), 아래 스크립트 코드를 작성해준다:

                                        
                                            
                                        
                                    
                                        
                                            const target_el= document.getElementById('target-ele') // 이벤트 타깃
                                            target_el.addEventListener('view', function(e) { // 이벤트 리스너 등록
                                                target_el.textContent= `이벤트 전달함!`
                                            });
                                            
                                            // 커스텀 이벤트 객체 생성(및 초기화)
                                            const evt_custom= new Event('view', { bubbles: false, cancelable: false });                                      
                                            target_el.dispatchEvent(evt_custom) // 만든 커스텀 이벤트를 타깃으로 전달한다
                                        
                                    

    이벤트를 전달받을 타깃 요소


    ✓   브라우저가 전송하는 이벤트는 이벤트 루프를 통해 비동기적으로 처리기들을 호출하지만, dispatchEvent()로 전달하는 이벤트는 처리기를 동기적으로(= 순서대로) 호출한다 - 즉, 모든 적합한 처리기의 호출과 반환이 끝나야 dispatchEvent() 역시 반환된다!

    1. 이벤트 객체에 더 많은 데이터를 추가하기 위해 CustomEvent 인터페이스가 존재하며, 여기서 detail 속성을 사용할 수 있다:

                                        
                                            
                                        
                                    
                                        
                                            const log_obj= document.querySelector('.p_para_obj')
    
                                            // CustomEvent 생성
                                            const evt_p= new CustomEvent("view", {
                                                bubbles: false, cancelable: false, detail: { name: "Kjc" }
                                            });
                                            
                                            // 적합한 이벤트 수신기 부착
                                            log_obj.addEventListener("view", (e) => log_obj.textContent= e.detail.name)
                                            
                                            // 이벤트 발송
                                            log_obj.dispatchEvent(evt_p) // Kjc
                                        
                                    

    이벤트 리스너에서는 e.detail.name 식으로 접근하여 값을 가져올 수 있다!

    2. 보통 하위 요소에서 이벤트를 전달, 트리거하고 상위 요소가 이 이벤트를 탐지하도록 하는 것이 바람직하다:
                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    
    3. 타깃.dispatchEvent(new Event('이벤트 이름'));는 비동기식으로 임의의 이벤트를 발생시킨다:

    [새로고침]한 뒤, 5초간 기다리세요..

                                        
                                            
                                        
                                    
                                        
                                            const box_element= document.querySelector('#dispatch_box')
    
                                            box_element.addEventListener('user', () => {
                                                box_element.textContent= '자동 커스텀 이벤트 발생 !'
                                            });
    
                                            setTimeout(() => {
                                                box_element.dispatchEvent(new Event('user')) // 자동 이벤트 생성
                                            }, 5000);
                                        
                                    

    돔 이벤트 요소

    입력폼 이벤트
    formsubmit(= 승인, 확인, 전송) 이벤트로 폼 전송하기:
                                        
                                            
                                        
                                    
                                        
                                            const formElement= document.querySelector('#form-submit')
            
                                            function handleSubmit(event) {
                                                const isYes= confirm('이 내용으로 제출하시겠습니까?')
                                                if (isYes === false) { event.preventDefault() } // 전송 취소
                                            };
    
                                            formElement.addEventListener('submit', handleSubmit)
                                        
                                    

    폼 전송 체크는 <form> 요소를 참조해야 한다!

    입력폼 이벤트 예)

    키보드 이벤트: keydown과 keyup, keypress
    keydown은 키를 누르는 시점인데, 이는 한중일 조합형 문자에서는 문제가 발생할 소지가 있다. 이런 연유로 키를 떼는 시점에서 발생하는 keyup 이벤트가 주로 쓰이지만, 이 또한 키를 꾸~욱 누르고 있을 때 하나로 인식되는 문제가 있어 게임에서 주로 사용하는 방향키는 keydown을 사용하는 것이 좋다! 한편, keypress는 문자키가 눌려진 시점인데, 문자 키에만 해당하기에 Alt, Ctrl, Shift, Enter 등의 변환문자 입력 시는 작동하지 않는다:

    Keydown 이벤트 예)

    현재 입력된 문자 수: 0

                                        
                                            
                                        
                                    
                                        
                                            
                                        
                                    

    keyup 이벤트 외에 keypress, keydown 등으로 바꿔서도 확인해보십시오..

    Keyup 이벤트 예)

    Key 값 가져오기

    모든 키의 올바른 키 값을 구하려면 key 프로퍼티를 사용하는데, 눌인 키 값은 문자열로 반환된다. code 프로퍼티는 눌린 키가 키보드에서 차지하는 물리적 위치를 뜻한다 - 곧, ShiftCtrl을 눌러도 바뀌지 않는다. 한편, keyCode 프로퍼티는 눌린 키의 아스키코드 값를 숫자 로 반환한다. 일반적으로 키코드 값은 알파벳과 숫자키만 올바르며, ShiftCtrl을 눌러도 바뀌지 않는다 그래서 알파벳 대/소문자 판정에는 keyCodeshiftKey 조합을 이용한다!

    • altKey/ctrlKey/shiftKey/metaKey 해당 키 metaKey는 맥의 커맨드키, 윈도우의 윈도우키이다!
    • repeat 키가 눌렸는가(리턴값: true/false)
    • isComposing 키 입력중인가 Shift 등 특수문자에 의한 입력변환 작업 중인 경우 true가 반환된다!
    마우스와 터치 이벤트
    1. click/dblclick은 타겟을 클릭/더블 클릭한 때이다. mouseenter는 바깥에서 타겟 내부로 처음 진입한 때(마우스 포인터가 내부 자식요소에 올라가면 이벤트가 해제된다), mouseleave는 타겟 바깥으로 막 나간 순간이다 - 여기서는 mouseover/mouseout과는 달리, 이벤트 버블링이 일어나지 않는다! 한편, mousemove는 타겟 내에서 마우스가 움직일 때인데, 지속적으로 마우스 움직임을 추적하려할 때 사용된다
    2. 터치 장치를 사용할 수 있는 장치에서는 touches 프로퍼티를 통해 동시 터치를 지원하거나 핀치, 스와이프 제스처 등의 터치 처리를 가능하게 할 수 있다: touchstart/touchmove/touchend는 터치 시작/움직임/종료 이벤트인데, 터치 장치의 터치 이벤트는 마우스 이벤트보다 우선한다 - 단, 터치 이벤트를 처리하지 않으면; 마우스 클릭 이벤트가 발생한다!

    터치 이벤트 예)

    마우스/터치 관련 속성

    마우스 클릭(및 터치 이동) 지점의 위치는 screenX/Y(스크린에서의 x, y 절대좌표), clientX/Y(브라우저), pageX/Y(뷰포트)로 확인할 수 있다 참고로, offsetX/Y는 이벤트가 발생한 요소 내에서의 상대 좌표이다!

    • relatedTarget 속성은 mouseenter(및 mouseover)에서는 마우스가 떠난 노드를, mouseleave(및 mouseout)에서는 마우스가 들어온 노드를 가리킨다
    • button 눌린 마우스의 버튼(0: 좌측버튼, 1: 휠, 2: 우측버튼)
    • changedTouches 배열로 된 터치 이동 정보
    탭 전환 이벤트
    Page Visibility API는 문서가 언제 표시되거나 숨겨지는지 알 수 있는 이벤트와 페이지가 현재 보이고 있는지 상태를 살펴볼 수 있는 기능을 제공한다. document 객체visibilitychange 이벤트는 브라우저의 탭(및 창, 앱) 활성화/비활성화 상태 변경 여부를 감지하며, visibilityState 이벤트는 탭 및 창 요소의 활성화/비활성화(visible/hidden) 상태를 표시한다

    탭 전환 이벤트 예)


    ✓  Page Visibility API는 사용자가 페이지를 보고 있지 않은 한 다음 슬라이드로 넘어가지 않아야 하는 이미지 캐러셀이 있는 사이트, 특정 정보에 대한 대시보드를 표시하는 애플리케이션이 페이지가 표시되지 않을 때 서버에 업데이트를 요청하고 싶지 않은 사이트, 페이지가 미리 렌더링 되는 시기를 감지하여 정확한 페이지 조회 수를 유지하려는 사이트, 디바이스가 대기 상태에 있을 때(예컨대, 절전모드로 전환하는 경우) 소리를 끄고 싶어 하는 경우 등에 유용하게 사용할 수 있다!

    Window 객체

    html 요소의 위치는 x, y축 좌표(px)로 표시하는데, html 문서는 브라우저의 좌상단을 원점으로 하며, 뷰포트는 문서 내용 표시영역(메뉴, 도구모음, 탭 등이 제외된 영역)의 좌상단을 원점으로 한다

    브라우저 창 크기변동 이벤트: resize
    window.resize 이벤트는 브라우저 창의 크기 변동이 있을 때 발생하는데, 이 이벤트는 오직 window 객체에서만 발생한다(전달된다). 이 resize 이벤트는 시스템에 상당한 부하를 줄 수 있으므로 requestAnimationFrame, setTimeout, customEvent 등을 사용해 이벤트를 쓰로틀하는 것이 좋다:

    브라우저 창크기 변동 이벤트 예)

    요소의 너비/높이
    브라우저 창 전체의 너비/높이는 window.outerWidth/outerHeight로 알 수 있고, 브라우저 창 내부 뷰포트의 너비/높이(문서 영역: 스크롤바와 테두리 포함)는 window.innerWidth/innerHeight로 알 수 있다(이는 탭, 프레임 등 창처럼 행동하는 모든 창 및 모든 객체에서 사용할 수 있다!)
    한편, html 문서 전체는 document.documentElement로 가져올 수 있는데, 이는 해당 문서의 루트, 곧 <html> 요소이다! 요소.clientWidth/clientHeight요소의 내부 너비/높이(패딩까지의 컨텐츠 영역 - 스크롤바는 제외)를 알 수 있는데, 루트 요소(<html> 또는 호환 모드 상에서의 <body>)에서 사용하면 스크롤바를 제외한 뷰포트 크기를 반환한다

    Css 좌표계에 관한 기본적 이해가 필요하시면; 본 Css 강좌의 Css 표준 좌표계 부분을 참조하십시오..

    요소의 크기 및 위치 정보
    요소.getBoundingClientRect() 메서드로 요소의 크기(박스의 테두리와 패딩 포함)와 위치(= 뷰포트 좌표) 값을 가져올 수 있는데, 반환값은 left/top(좌상단 모서리), right/bottom(우하단 모서리), width/height(너비/높이) 속성이 있는 DOMRect 객체 컬렉션이다 이는 Css에서 box-sizing: border-box;로 설정된 때의 width, height와 같다!

    요소의 크기 및 위치정보 예)


    반대로, 뷰포트에서 특정 좌표에 있는 요소가 무엇인지 알아야할 때도 있는데, 이때는 Document 객체elementFromPoint() 메서드를 사용할 수 있다: 원하는 x, y 좌표값(예컨대, 마우스 이벤트의 clientX, clientY 값)을 주어 이 메서드를 호출하면; 지정된 위치에 있는 Element 객체가 반환된다 이 메서드의 의도는 해당 지점에서 가장 안쪽의(곧, 가장 깊이 중첩된) 요소 및 가장 위쪽의(곧, z-index 값이 가장 큰) 요소를 반환하는 것이다!

    윈도우 창 스크롤 이벤트: scroll
    요소.scroll 이벤트는 대상 요소 의 스크롤 작업시 발생하는데, 주로 Window 객체에서 사용된다
                                        
                                            window.addEventListener('scroll', () => {
                                                console.log('스크롤 작업: ', window.scrollX, window.scrollY)
                                            })
                                        
                                    

    window.scrollX/scrollY(및 pageXOffset/pageYOffset)는 수평/수직으로 스크롤한 픽셀값, 곧 현재 뷰포트 좌상단 모서리의 (x, y) 좌표가 된다!

    윈도우 창 스크롤 작업)

    1. 요소.scroll(x, y) 메서드는 문서좌표 기준인 x, y 좌표를 받아 이 값을 스크롤 오프셋으로 설정한다 - 곧, 브라우저 창을 스크롤해서 지정된 지점을 뷰포트의 좌측 상단 모서리로 만드는 것이다:
                                        
                                            let documentHeight= document.documentElement.offsetHeight // 문서 높이
                                            let viewportHeight= window.innerHeight // 뷰포트 높이
    
                                            // 마지막 페이지가 보이도록 스크롤한다
                                            window.scrollTo(0, documentHeight - viewportHeight)
                                        
                                    

    참고로, scrollTo(x, y)scroll(x, y)와 사실상 같은 움직임을 보인다!

    2. 요소.scrollBy(dx, dy)(dx, dy) 만큼 상대적으로 이동한다: window.scrollBy({ top: 50, left: 50 }) 아래로 50px, 옆으로 50px 스크롤
                                        
                                            if (window.scrollY) { // y축 방향으로 스크롤했다면;
                                                window.scroll(0, 0) // 스크롤을 문서의 (0, 0) 위치로 재설정
                                            }
    
                                            window.scrollBy(0, window.innerHeight) // 한 페이지 아래로 스크롤
                                            // window.scrollBy(0, -window.innerHeight) // 한 페이지 위로 스크롤
                                        
                                    

    ✓   behavior 옵션을 주어 스크롤 움직임을 설정해줄 수도 있다:

                                        
                                            window.scrollTo({
                                                left: 0,
                                                top: documentHeight - viewportHeight,
                                                behavior: "smooth" // 부드러운 스크롤
                                            });
                                        
                                    

    behavior: 속성값에는 "instant" (단번에 스크롤), "auto" (브라우저 및 스크롤 대상요소나 Css에 설정된 scroll-behavior 속성에 따른다)도 있다!

    3. 실제로는, 일정 픽셀만큼 스크롤하기보다는 원하는 요소가 (뷰포트에)보일 때까지 스크롤하는 경우가 일반적인데, 원하는 html 요소에서 요소.scrollIntoView([false]) 메서드를 사용해주면; 호출된 요소의 위쪽 경계가 뷰포트의 상단에 맞추어지며(곧, 호출된 요소가 사용자에게 표시되도록 요소의 상위 컨테이너를 위로 말아올린다!), false 옵션값을 주면; 호출된 요소의 아래쪽 경계가 뷰포트의 하단에 맞추어진다
    [ scrollIntoView() 옵션객체]
                                            
                                                const el= document.querySelector("#box")
    
                                                el.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" })
                                            
                                        

    block: "start/center/end/nearest" 옵션은 세로 스크롤을 설정하며(기본값: start), inline: "start/center/end/nearest" 옵션은 가로 스크롤을 설정한다(기본값: nearest)

    요소의 스크롤 오프셋

    1. 브라우저 창만 아니라 html 요소에도 스크롤바가 나타날 수 있는데, 이런 경우 뷰포트 크기와 컨텐츠 크기 및 컨텐츠의 스크롤 오프셋을 알아야 할 때가 있다:

    • 요소의 x, y 좌표: 요소.offsetLeft/offsetTop 이는 대개 문서 좌표이지만, 포지션이 지정된 요소의 자손이나 테이블 셀 등에서는 컨테이너에 상대적인 좌표이다!
    • 화면에 표시된 요소의 크기: 요소.clientWidth/clientHeight(요소의 컨텐츠 영역과 패딩까지), 요소.offsetWidth/offsetHeight(요소의 테두리까지)

    2. 요소의 스크롤 크기는 요소.scrollWidth/scrollHeight로 알 수 있는데, 이는 컨텐츠 영역과 패딩, 넘치는 컨텐츠까지 포함한다. 한편, 요소.scrollLeft/scrollTop(요소의 뷰포트 안에 있는 컨텐츠의 스크롤 오프셋)은 여타 속성들과는 달리 쓰기 가능한 프로퍼티이므로, 이를 써서 요소 안에서 컨텐츠를 스크롤할 수 있다!

    Location 객체

    location은 객체가 연결된 장소(= URL)를 표현한다. location에 변경을 가하면 연결된 객체에도 반영되는데, Document.location이나 Window.location으로 접근할 수 있다. 곧, 여기 문자열을 할당하면; 브라우저는 그 문자열을 URL 로 간주하여 현재 문서를 대체하게 된다:

    Location 객체
    URL 관련 객체에는 window.location, document.location으로 참조하는 Location 객체가 있고, URL() 생성자 및 document.URL(반환값은 객체가 아닌 현재 문서의 URL 이 담긴 문자열이다!) 속성도 있다
                                        
                                            document.locatin= "http://www.sosohan.xyz" // 절대 URL로 이동
                                            document.location= "/about#history=express" // (현재 문서를 기준으로 하는)상대 URL 및 (id가 지정된)해시로 바로 이동하는 것도 가능하다!
    
                                            document.location= "#top" // 현재 문서의 최상단으로 이동한다
                                        
                                    
                                        
                                            location.href= "http://www.sosohan.xyz/about?test=1#history=express" // URL 전체
    
                                            location.path= "/about2" // 페이지 이동
                                            location.search= "?test=2" // 검색 매개변수 변경
                                            location.hash= "#history=v-pills-LocationObj" // 해시 추가
                                        
                                    

    location.reload([true])는 현재 페이지를 다시 읽어오는데(= F5키), true 옵션을 주면; (게시판에서 새 글을 확인하는 등의 경우에)브라우저 캐시를 무시하고 [새로고침]을 실행한다. location.assign(URL)(= document.location= "URL")은 현재 문서에 새 주소를 할당하여 이동하는데, history 스택에 추가되며 [뒤로] 버튼을 통해 (현재 문서로)다시 돌아올 수 있다. 반면, location.replace(URL)assign()과 같은 방식으로 작동하되, 현재 문서에 대한 history 스택이 초기화되어, [뒤로] 버튼을 누르면; (현재 문서가 아니라)그 이전 문서로 돌아가게 된다!

    Window.hashchange 이벤트
    Window.hashchange 이벤트는 윈도우의 (#으로 시작하는)hash 상태가 변경되면 시작된다:

    * 현재 hash 위치는?

                                        
                                            
                                        
                                    
                                        
                                            function funcRef() {
                                                const hash= location.hash
                                                document.querySelector('.log-hash_ex').textContent= `현재 위치는 「${hash}」입니다!`
                                            }
    
                                            window.addEventListener('hashchange', funcRef) // 해시값이 바뀌면; funcRef() 함수를 실행한다
                                        
                                    

    참고로, 이 이벤트는 history.pushState()history.replaceState()에 의해 해시가 수정될 때는 발생하지 않는다!

    History 객체

    전역 Window에서만 접근 가능한 History APIhistory 객체를 통해 브라우저의 세션 기록에 대한 접근을 제공하는데, 이를 이용하면 사용자의 방문 기록을 앞뒤로 탐색하고, 방문 기록 스택의 내용을 조작할 수 있게 된다!

    History API
    1. Window.popstate 이벤트는 사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생하는데, 브라우저의 뒤로, 앞으로 버튼이나 history.go()(= 새로고침), back()(= 뒤로), forward()(= 앞으로) 같은 메서드를 호출할 때 발생한다

    ✓   history.go() 메서드는 이동할 단계를 지정해줄 수 있다: go(-1/0/1)(= 뒤로/새로고침/앞으로), go(-2/2)(= 2단계 뒤로/다음으로)

    2. history.pushState(상태객체, "", URL)는 Window의 popstate 이벤트와 함께 작동하여 웹 페이지 열람 이력을 추가한다. 한편, history.replaceState(상태객체, "", URL)는 특정 사용자 작업에 대한 응답으로 현재 히스토리 항목의 상태객체(생성된 새 히스토리 항목과 연관된 JavaScript 객체로서, 사용자가 브라우저를 재시작한 후 복원할 수 있도록 사용자의 디스크에 저장된다!)나 URL 을 업데이트하려는 경우에 사용된다

    ✓   두번째 인자는 상태의 타이틀을 나타낼 문자열이 들어가지만, 대부분의 브라우저에서 지원하지 않기에 빈 문자열을 넣어주면 된다. 마지막 인자는 새 히스토리 항목의 URL 인데(생략 시; 문서의 현재 URL), 실제 페이지 이동은 하지 않으며, 새 URL이 상대 URL인 경우 현재 URL을 기준으로 하며, 새 URL은 현재 URL과 동일출처여야 한다!

    3. 만약, 활성화된 엔트리가 history.pushState()history.replaceState()에 의해 생성되면, Popstate 이벤트state 속성은 히스토리 엔트리 state 객체의 복사본을 갖게된다. 곧, history.pushState()history.replaceState()Popstate 이벤트를 발생시키지 않는다!

    히스토리 이벤트 예)


    이 API의 주요 목적은 ReactVue와 같이 가상 DOM을 사용하는 싱글 페이지 애플리케이션 SPA 웹사이트를 지원하는 것이다. 이러한 웹사이트는 SPA 라우터에서의 페이지 이동에, fetch()와 같은 JavaScript API를 사용하여 페이지 전부를 새로 불러오는 대신, 새로운 컨텐츠만으로 페이지를 업데이트한다. 이에 서버에서 새 페이지를 로드하지 않고도 웹사이트를 사용할 수 있으므로, 웹사이트의 속도를 높일 수 있게된다!

    Web Storage API

    Web Storage는 사용자 측에 이름: 값 쌍의 문자열로 구성된 데이터를 저장하는 메카니즘으로서 쿠키와 다르게 HTTP 헤더를 통한 조작이 불가능하며, 서버로는 전송되지 않는다. 또한, 동일출처 정책을 따른다. 따라서, 게시글의 임시 저장이나 다크테마 저장 같이 개인에 맞춰진 UI 상태를 저장하기에 적합하다!

    로컬/세션 스토리지 객체
    Window 객체의 localStroage 객체는 브라우저에서만 데이터를 유지할 수 있다는 단점이 있지만, 로그인 없이도 사용자 데이터를 저장할 수 있다는 커다란 장점이 있다 - 곧, 브라우저에 저장된 정보와 fetch() API 접근을 이용하면 서버를 통한 페이지 렌더링을 한번으로 줄일 수 있게 된다!
                                        
                                            let name= localStorage.username // 스토리지에 저장된 username 값
    
                                            if (! name) {
                                                name= prompt("너 이름이 어떻게 돼?")
                                                localStorage.username= name // 사용자의 응답을 받아 스토리지에 저장한다
                                            }
                                        
                                    
    1. localStorage 객체는 브라우저 차원에서 기간 제한없이 데이터를 저장하고(곧, 브라우저를 닫았다 열어도 데이터는 그대로 남는다!), 동일 출처(및 동일 브라우저)의 여러 창과 탭에서 공유할 수 있다. 반면, sessionStorage 객체는 페이지를 새로 고침하는 경우에는 저장한 데이터가 유지되지만, 페이지를 떠나면 저장된 데이터도 함께 삭제된다는 점에서 차이가 있다. 나아가, 세션스토리지는 페이지 세션이 유지되는 동안(곧, 브라우저나 탭이 열려있는 동안) 각각의 탭에 대해서 (같은 페이지를 연 경우에도!)서로 독자적인 저장 공간을 제공한다
    2. 세션스토리지는 서버측 렌더링과 클라이언트측 기능이 혼합되어 있는 경우에 유용하다. 페이지를 새로 고침하면; 저장한 데이터가 유지되고, 사용자가 페이지를 떠났다가 다시 돌아오면; 비어있는 기본 상태로 된다(최신 브라우저들에서는, 최근에 닫은 탭을 다시 열면 마지막 세션을 복원해주기도 한다!). 이처럼 브라우저에 저장된 정보와 fetch() API 접근을 이용하면 서버를 통한 페이지 렌더링을 한번으로 줄일 수 있게 된다!

    웹 스토리지 예)


    로컬/세션 스토리지의 프로퍼티는 delete 연산자를 써서 제거할 수도 있고, Object.keys()for .. in 루프를 써서 열거할 수도 있다. 또한, 아래와 같은 메서드들도 사용할 수 있다:

    • localStorage.setItem(키, 값) 스토리지에 (객체와 같은 방식으로)키와 값 저장
    • localStorage.getItem(키) 저장된 값 가져오기(없으면; null) localStorage.키, localStorage['키'] 방식으로도 사용할 수 있다!
    • localStorage.removeItem(키) 저장된 특정 키 삭제
    • localStorage.clear() 스토리지 키 전부 비우기
    • localStorage.key(인덱스) 인덱스 번호에 해당하는 키
    • localStorage.length 저장된 항목의 개수

    ✓   일반 객체와는 달리, 스토리지에 저장되는 데이터는 항시 문자열로 저장되므로 문자열이 아닌 데이터를 저장할 때는 직접 인코딩/디코딩해주어야 한다:

                                        
                                            localStorage.x= 10 // "10" ← 이 숫자는 자동으로 문자열로 변환되어 저장된다!
                                            let x= parseInt(localStorage.x) // 10 ← 가져와서 사용할 때는 다시 숫자로 파싱해주어야 한다!
    
                                            localStorage.lastRead= new Date().toUTCString(); // 날짜를 문자열로 저장함
                                            let lastRead= new Date(Date.parse(localStorage.lastRead)); // 가져올 때는 다시 파싱하여 사용하여야 한다!
    
                                            localStorage.data= JSON.stringify(data) // 인코딩 후 저정한다
                                            let data= JSON.parse(localStorage.data) // 파싱하여 가져온다
                                        
                                    
    3. 스토리지의 데이터가 변경될 때마다, 그 데이터를 볼 수 있는 모든 창 및 탭에서 storage 이벤트가 발생하는데, 이 이벤트는 변경을 일으킨 창(및 탭) 외의 해당 스토리지에 접근 가능한 다른 창(및 탭)에서 발생하고 수신된다: window.addEventListener('storage', (e) => { console.log(e) })
    • key 스토리지 이벤트 객체의 바뀐 키 clear() 시는 null 을 반환한다
    • oldValue, newValue 이전 키, 새로운 키 새로 추가된 경우는 oldValue가, 제거된 경우에는 newValuenull 이 된다
    • url 키를 바꾼 문서의 URL
    • storageArea 영향을 받은 스토리지 객체(localStorage나 sessionStorage)

    ✓   localStroage(및 stroage 이벤트)는 같은 웹사이트를 방문 중인 창(및 탭) 전체에 메시지를 전송하는 역할을 할 수 있다. 예컨대, 사용자가 웹사이트의 배경 음악을 중지하도록 요청한 때, 웹사이트는 이를 localStorage에 저장해서 사용자가 다시 방문할 때 이를 적용하고, 이는 또한 같은 사이트를 표시하는 창 및 탭 전체에 적용되는 것이다!

    ➥ 쿠키에 관하여

    쿠키 Cookie는 웹서버가 사용자의 브라우저로 보내는 기록용 정보파일인데, 오래되고 이해하기에도 어려운데다, 또 보안상의 많은 문제점들도 있어 최근에는 Web StorageIndexedDB API 같은 새로운 저장소를 쓰는 것을 권장하고 있습니다. 쿠키는 아직도 많이 쓰이기는 하지만, 굳이 지금 와서 늦게 억지로 배우는 것도 좀 그렇고.. 그래서 여기서도 이런게 있다 소개만 하고, 그냥 건너뜁니다 ㅡㅡ;

    wave