js/browser – DOM(Document Object Model)
DOM은 프로그래머 관점에서 바라 본 HTML
DOM 이해하기
DOM은 Document Object Model의 약자로, HTML 요소를 Object(JavaScript Object)처럼 조작(Manipulation)할 수 있는 Model입니다. 즉, 여러분이 자바스크립트를 사용할 수 있으면, DOM으로 HTML을 조작할 수 있습니다.
학습 목표
* DOM의 개념
* DOM의 구조와 HTML과 비교
* HTML에서 JavaScript 파일을 불러올 때의 주의점
- `<script>` 태그가 적용되는 위치에 따라서 실행 결과가 달라짐
* DOM과 JavaScript의 차이
HTML에 JavaScript 적용하기
<script>
태그를 사용
<script src="myScriptFile.js"></script> // html 파일과 같은 디렉토리에 있는 ~파일 불러옴
→ 웹 브라우저는 코드를 해석하는 과정에서 <script>
요소를 만나면 html 해석을 일시 정지하고 <script>
요소를 먼저 실행한다. 그 후에 다시 html 해석
→ <script>
요소는 등장과 함께 실행된다
<script>
태그를 추가하는 2가지 방식
1.<head>
안쪽에 삽입하는 경우
<head>
태그에 추가하는 경우로 html 문서의 head 영역에 script 요소가 포함됨
2.<body>
태그가 끝나기 전에 삽입하는 경우
가 끝나기 전에 추가하는 방법으로 html 문서의 body 영역의 끝에 script 요소가 포함됨
두 방식의 공통점과 차이점?
두 방식 모두 myScriptFile.js 내의 첫 번째 console.log를 성공적으로 출력하지만,
두 번째 console.log의 경우 제대로 출력하지 못하는 예시
console.log('welcome JavaScript'); // 'welcome JavaScript' 두 방식 모두 정상 출력
let msgElement = document.querySelector('#msg');
console.log(msgElement); // head방식: null 출력 body방식: <div id="msg">Hello JavaScript!</div> 출력 (boby 내에 msg라는 id가 존재하고 그 내용이 Hello JavaScript!)
왜 그럴까?
→ 1번 방식에서 null 이 출력 되는 것은 head 방식이므로 html을 다 해석하기도 전에 script 태그를 만나므로 2번째 console.log에서 참조할 id를 알 수 없다(아직 html을 읽어들이지도 못했으니깐)
→ 즉, html 엘리먼트를 이용하려면 2번 방법(body방식)을 사용해야 할 것이다
JavaScript에서의 html 표현
<html>
<body>
<div id="nav">
<div class="logo"></div>
<div class="menu-wrapper">
<div class="menu"></div>
<div class="menu"></div>
<div class="menu"></div>
<div class="profile-photo"></div>
</div>
</div>
<div id="news-contents">
<div class="news-content-wrapper">
<div class="news-picture"></div>
<div class="news-title"></div>
<div class="news-description"></div>
</div>
</div>
<div id="footer"></div>
</body>
</html>
위의 예시를 통한 구조 분석하기
Q1. body 엘리먼트의 자식 엘리먼트는 총 몇개?
Q2. id의 이름이 news-contents인 div 엘리먼트의 부모 엘리먼트는?
Q3. id의 이름이 nav인 div엘리먼트를 포함해서 모든 자식 엘리먼트의 class 이름을 console.log를 사용하여 확인하는 방법을 수도코드로 생각해보자
// 만약 id값이 nav라면 nav를 출력
// 그 안에서 또 만약, class의 값이 logo거나 menu-wrapper라면 그것을 출력?
→ 이런 구조 분석하는 게 html 자체만 보고 쉽지 않다
→ 그래서 자바스크립트라는 프로그래밍 언어와 DOM을 활용하여 HTML에 접근하고 조작
Q1 - 자식 엘리먼트 찾기
A) 3개: nav, news-contents, footer
자바스크립트에서 DOM은 document 객체에 구현되어 있다. 브라우저에서 작동되는 자바스크립트 코드에서는, 어디에서나 document 객체를 조회할 수 있음
→ console.dir이 유용 : DOM을 객체의 모습으로 출력
console.dir(document.body);
Q2 - 부모 엘리먼트 찾기
A) body 엘리먼트
//document.body.children 으로 body의 자식 엘리먼트 찾고 여기서 1번 key인 news-contents를 변수로 저장
let newsContents = document.body.children[1];
newsContents.parentElement; // <body>...</body> 출력
사용법: node.parentElement
Q3 – DOM 순회하기
html 구조는 트리 구조. 트리 구조에서 부모가 가진 하나 또는 여러 개의 자식 엘리먼트를 조회하는 코드를 작성한다면, 여러 번 반복해서 실행하는 코드가 필요
function consoleLogAllElement(element){
// nav의 class 이름을 console.log 합니다.
// nav의 자식 엘리먼트가 있는지 검색합니다. (logo, menu-wrapper)
//logo의 class 이름을 console.log 합니다.
//logo의 자식 엘리먼트가 있는지 검색합니다. (없음)
//menu-wrapper의 class 이름을 console.log 합니다.
//menu-wrapper의 자식 엘리먼트가 있는지 검색합니다. (menu, menu, menu, profile-photo)
//첫 번째 menu의 class 이름을 console.log 합니다.
//첫 번째 menu의 자식 엘리먼트가 있는지 검색합니다. (없음)
//두 번째 menu의 class 이름을 console.log 합니다.
//두 번째 menu의 자식 엘리먼트가 있는지 검색합니다. (없음)
//세 번째 menu의 class 이름을 console.log 합니다.
//세 번째 menu의 자식 엘리먼트가 있는지 검색합니다. (없음)
//profile-photo의 class 이름을 console.log 합니다.
//profile-photo의 자식 엘리먼트가 있는지 검색합니다 (없음)
//자식 엘리먼트를 모두 탐색했음으로, 함수 실행이 종료됩니다.
//자식 엘리먼트를 모두 탐색했음으로, 함수 실행이 종료됩니다.
}
DOM으로 HTML 조작하기
CRUD(Create, Read, Update and Delete)를 통한 연습
→ document 객체를 통해서 HTML 엘리먼트를 만들고(CREATE), 조회하고(READ), 갱신하고(UPDATE), 삭제하는(DELETE) 하는 방법 연습
DOM에는 HTML에 적용(APPEND)하는 메소드가 따로 있으니 주의
학습 목표
* DOM을 JavaScript로 조작하여 html Element를 추가, 삭제, 변경하기
- CREATE: createElement
- READ: querySelector, querySelectorAll
- UPDATE: textContent, id, classList, setAttribute
- DELETE: remove, removeChild, innerHTML=””, textContent=””
- APPEND: appendChild
- innerHTML과 textContent의 차이
심화 목표
- createDocumentFragment를 활용한 DOM 제어
- HTML5 template tag 사용법
- element와 node의 차이
- children과 childNodes의 차이
- remove와 removeChild의 차이
- 같은 엘리먼트를 appendChild하면 기존 엘리먼트를 복사할까?
- offsetTop: 좌표 정보 조회
- offsetWidth: 크기 정보 조회
CREATE – createElement
새로운 element를 생성
document.createElement('div') // document 바로 하단에 div 엘리먼트 생성
자바스크립트에서 어떤 작업의 결과를 담으려면?
→ 변수를 선언하고 어떤 작업의 결과를 변수에 할당
여기서는 div element를 변수 tweetDiv 에 할당
const tweetDiv = document.createElement('div')
→ 화면 상에는 아무 변화가 없다 현재 tweetDiv 요소는 공중부양 상태
아무 node와도 연결이 되어 있지 않은 하나의 node 상태다
→ 이렇게 공중부양된 엘리먼트 확인하기 위해서는 APPEND 해야 보인다.
APPEND – append, appendChild
tweetDiv(공중부양된 엘리먼트)를 트리 구조와 연결하는 작업
→ append 라는 메소드를 사용해서, 변수 tweetDiv 를 body 안에 넣기
document.body.append(tweetDiv) // 변수 tweetDiv에 담긴 새로운 div 엘리먼트를 body 엘리먼트에 append(적용)
READ – querySelector, querySelectorAll
DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는 querySelector의 첫 번째 인자로 셀렉터(Selector)를 전달하여 확인
→ 셀렉터로는 HTML 태그("div"), id("#tweetList"), class(.tweet) 세 가지가 가장 많이 사용
querySelector
querySelector 에 '.tweet' 을 첫 번째 인자로 넣으면, 클래스 이름이 tweet 인 HTML 엘리먼트 중 첫 번째 엘리먼트를 조회
const oneTweet = document.querySelector('.tweet')
querySelectorAll
여러 개의 엘리먼트를 한 번에 가져오기 위해서는, querySelectorAll 을 사용
이렇게 조회한 HTML 엘리먼트들은 배열처럼 for문을 사용하실 수 있습니다. 주의하세요! 앞서 조회한 HTML 엘리먼트들은 배열이 아닙니다! 이런 '배열 아닌 배열'을 유사 배열, 배열형 객체 등 다양한 이름으로 부릅니다. 정식 명칭은 Array-like Object
const tweets = document.querySelectorAll('.tweet') // querySelectorAll로 클래스 이름이 tweet 인 모든 HTML 엘리먼트를 유사 배열로 받아 옴
cf.) get으로 시작하는 DOM 조회 메소드: querySelector와 비슷한 역할을 하는 옛날 방식의 메소드
→ 브라우저 호환성(이전 버전 등) 등의 문제로 쓰는 경우도 있다
const getOneTweet = document.getElementById('container') //get 메소드
const queryOneTweet = document.querySelector('#container')
console.log(getOneTweet === queryOneTweet) // true
생각해보기
querySelector에 대한 심화 학습
querySelector의 첫번째 인자에 ‘div’를 넣으면 어떻게 될까? //문서 내 첫번째 div를 출력?
querySelector를 통해서 더 복잡한 작업이 가능? // #(id) .(class) input 등을 이용하면 되긴 함
QuerySelector의 부모는 꼭 document 객체여야 하나?
UPDATE – textContent, classList.add
기존에 생성한 빈 div 태그를 업데이트하여, 보다 다양한 작업 해보기
→ textContent 를 사용해서, 비어있는 div 엘리먼트에 문자열을 입력
console.log(tweetDiv) // <div></div>
tweetDiv.textContent = 'dev';
console.log(tweetDiv) // <div>dev</div>
앞서 생성한 div 엘리먼트를 container에 append 했을 때, CSS 스타일링이 적용되지 않았는데 CSS 스타일링이 적용될 수 있도록, div 엘리먼트에 class를 추가
→ classList.add(‘클래스명’)
tweetDiv.classList.add('tweet')
console.log(tweet) // <div class="tweet">dev</div>
clss와 id 외에 다른 attribute를 추가하는 것은?
Element.setAttribute(name, value);
DELETE – remove, removeChild
삭제하는 방법에도 여러가지가 존재
remove
삭제하려는 엘리먼트의 “위치”를 알고 있는 경우에 사용하는 방법
→ node.remove()
const nav = document.querySelector('#nav')
const tweetDiv = document.createElement('div')
nav.append(tweetDiv)
tweetDiv.remove() // 이렇게 append 했던 엘리먼트를 삭제할 수 있다.
innerHTML
여러 개의 자식 엘리먼트를 지우는 경우 사용
→ innerHTML을 활용
→ 모든 자식 엘리먼트가 지워짐!
document.querySelector('#container').innerHTML = '';
//id가 container인 엘리먼트 아래의 모든 엘리먼트를 지웁니다
→ 그런데 innerHTML은 보안에서 몇가지 문제를 가지고 있다!
→ 내용이 보호되지 않고 조회해서 다 보이는 듯?(보안상 위험 존재)
`<script>` tag를 활용하여 강제로 해커가 원하는 스크립트를 실행시키는 XSS Attack이 대표적
→ 다른 메소드를 사용하는 게 좋다; removeChild
removeChild
자식 엘리먼트를 지정해서 삭제하는 메소드
모든 자식 엘리먼트를 삭제하기 위해서 반복문 활용(while, for , …)
→ node.removeChild(엘리먼트키워드) // 엘리먼트키워드: node.firstChild, lastfirChild, ...
ex) 자식 엘리먼트가 남아있지 않을 때까지, 첫 번째 자식 엘리먼트를 삭제하는 코드
const container = document.querySelector('#container');
while (container.firstChild) {
container.removeChild(container.firstChild);
}
→ 그런데 removeChild와 while을 이용해 자식 요소를 삭제하면 제목에 해당하는 H2”Tweet List” 까지 삭제된다. 이를 방지하기 위한 방법은?
case1) 자식 엘리먼트가 1개만 남을 때까지, 마지막 자식 엘리먼트를 제거
const container = document.querySelector('#container');
while (container.children.length > 1) {
container.removeChild(container.lastChild);
}
case2) 클래스 이름이 tweet인 엘리먼트만 찾아서 제거
const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
tweet.remove();
})
// or
for (let tweet of tweets){
tweet.remove()
}
심화 학습을 위해 생각해보기
* element와 node의 차이(difference between element and node in javascript dom)
* children과 childNodes의 차이(difference between children and childNodes in javascript dom)
* removeChild와 remove의 차이(difference between removeChild and remove in javascript dom)
* tweets에 forEach는 되는데, reduce는 안되는 이유(why array method is not working on nodelist)
* tweets를 유사 배열에서 배열로 바꾸는 방법 (how to convert nodelist into javascript array)
아이디가 "javascript"이고, 내용이 "awesome"인 a 태그를 생성하는 방법
case1)
let aElement = document.createElement('a')
aElement.setAttribute('id', 'javascript')
aElement.textContent = 'awesome'
aElement // <a id=”javascript”>awesome</a>
case2)
let aElement = document.createElement('a')
aElement.id = 'javascript'
aElement.innerHTML = 'awesome'
aElement // <a id=”javascript”>awesome</a>
insertBefore(), cloneNode(), appendChild(), prependChild(), insertAfter()
사용법은 mdn 검색…
버튼을 클릭하면 안내창 문구가 나오게 하는 방법?
<body>
<div>
<div>hello</div>
<div id="world">world</div>
<span id="code">code</span>
<span>states</span>
<button id="apply">apply</button> // 해당 버튼
</div>
</body>
case1) DOM 객체에 onclick을 직접 지정
function displayAlert() {
alert('안내창입니다’)
}
document.querySelector('#apply').onclick = displayAlert
// 함수를 할당할 때 함수의 실행값(displayAlert() )을 할당하면 안되고
// 함수 그 자체( displayAlert)를 할당해야 한다!
case2) addEventListener 메소드를 사용해서 할당
document.querySelector('#apply').addEventListener('click', function(){
alert("안내창입니다")
})
'SE Bootcamp 내용 정리' 카테고리의 다른 글
js/browser - DOM -2 (0) | 2021.09.13 |
---|---|
js/browser - 유효성 검사 실습 (0) | 2021.09.10 |
얕은 복사, 깊은 복사 내용에 대한 정리? (0) | 2021.09.10 |
js/node - spread/rest 문법 (0) | 2021.09.09 |
js/node - 자료형, 스코프, 클로저 (0) | 2021.09.08 |