[ unit 11 ] DOM

uxolrv·2022년 7월 14일
0

👊  학습 목표

  • DOM의 개념을 이해한다.
  • DOM의 구조를 파악하고, HTML과 DOM이 어떻게 닮아있는지 이해한다.
  • HTML에서 JavaScript 파일을 불러올 때 주의점에 대해서 이해한다.
  • DOM을 JavaScript로 조작하여 HTML Element를 추가할 수 있다. (CREATE)
  • DOM을 JavaScript로 조작하여 HTML Element를 조회할 수 있다. (READ)
  • DOM을 JavaScript로 조작하여 HTML Element를 변경할 수 있다. (UPDATE)
  • DOM을 JavaScript로 조작하여 HTML Element를 삭제할 수 있다. (DELETE)
  • 생성한 HTML Element를 부모 엘리먼트의 자식 엘리먼트로 포함할 수 있다. (APPEND)
  • innerHTML과 textContent의 차이를 이해한다.



👊  심화 학습 목표

  • DOM과 JavaScript의 차이에 대해 이해할 수 있다.
  • createDocumentFragment를 활용하여, 더 효율적으로 DOM을 제어할 수 있다.
  • HTML5 template tag 사용법을 이해할 수 있다.
  • element와 node의 차이를 이해할 수 있다.
  • children과 childNodes의 차이를 이해할 수 있다.
  • remove와 removeChild의 차이를 이해할 수 있다.
  • 같은 엘리먼트를 appendChild 하면, 기존 엘리먼트를 복사할까?
  • offsetTop 등을 이용하여 좌표 정보를 조회할 수 있다.
  • offsetWidth 등을 이용하여 크기 정보를 조회할 수 있다.





📌  DOM이란?

“HTML 요소를 Object(JavaScript)처럼 조작(Manipulation)할 수 있는 Model”

"Document Object Model"의 약자
DOM으로 HTML을 조작할 수 있음
즉, HTML로 구성된 웹페이지를 동적으로 움직이게 만들 수 있음






📌  HTML에 JavaScript 적용하기

HMTL에 JavaScript를 적용하기 위해서 <script> 태그를 이용함

<script src="myScriptFile.js"></script>



⬆️  브라우저 동작 과정

파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미함
웹브라우저가 작성된 코드를 해석하는 과정에서
<script> 요소를 만나면 웹 브라우저는 HTML 해석을 잠시 멈춤
HTML 해석을 잠시 멈춘 웹 브라우저는 <script> 요소를 먼저 실행함

<script> 요소는 등장과 함께 실행된다는 사실을 꼭 기억!!


💡 script 요소를 추가하는 대표적인 사례 2가지

// myScriptFile.js 파일 내용
console.log("welcome JavaScript");

let msgElement = document.querySelector("#msg");
console.log(msgElement);

1️⃣ <head> 안쪽에 삽입하는 경우

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
<!-- script 요소 삽입 위치 -->
<script src="myScriptFile.js"></script>
</head>
<body>
<div id="msg">Hello JavaScript!</div>
</body>
</html>
화면콘솔
  Hello JavaScript!    welcome JavaScript  
null



2️⃣ </body> 요소가 끝나기 전에 삽입하는 경우

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<div id="msg">Hello JavaScript!</div>
<!-- script 요소 삽입 위치 -->
<script src="myScriptFile.js"></script>
</body>
</html>
화면콘솔
  Hello JavaScript!    welcome JavaScript  
<div id="msg">Hello JavaScript!</div>

두 방식 모두 myScriptFile.js 내의 첫번째 console.log를 성공적으로 출력하지만
두 번째 console.log가 제대로 출력되지 않음



Q. 각 방법은 콘솔 출력이 어떻게 다른가요?
<head> 안쪽에 삽입하는 경우 : null
</body> 요소 전에 삽입하는 경우 : <div id="msg">Hello JavaScript!</div>

Q. 1번 방법에서는 null 이 콘솔에 출력되는 이유가 무엇일까요?
HTML 파싱 과정에서 <script>를 만나면 <script>가 먼저 실행되는데
이때, 자바스크립트에서 아직 읽히지 않은 html 요소에
접근하는 코드가 작성되어 있기 때문에 null이 출력됨

Q. HTML 요소를 이용하려면 1번과 2번 중 어떤 방법을 사용해야 할까요?
</body> 요소가 끝나기 전에 삽입해야
HTML 코드를 전부 읽은 후 <script>가 실행됨






💡 DOM 구조는 트리 구조

⬆️  HTML과 DOM의 구조

HTML은 단순히 규칙에 따라 정해진 태그, 속성값으로 이루어진 언어이며,
DOM은 브라우저가 HTML 파싱한 후 생성되는 객체 모델로, document에 접근가능한 API
API : application programming interface
      운영체제와 응용프로그램 사이의 통신에 사용되는 언어나 메시지 형식

HTML 문서를 파싱하는 과정에서 단순 텍스트로 구성된 HTML문서의 내용과 구조가 객체 모델(DOM)으로 변환되어 다양한 프로그램에서 사용될 수 있음

⬆️  DOM의 트리 구조

DOM 구조는 회사의 조직도와 유사
<body>가 가장 상위에 있고 아래에 여러 구성요소가 부모-자식 관계를 가지고 있음

이런 자료 구조를 컴퓨터 공학에서는 트리 구조라고 함.
트리 구조는 부모가 자식을 여러 개 가지고, 각각의 자식이 다시 자식을 갖는 구조가 반복되는 것이 특징

만약, 부모가 가진 하나 또는 여러 개의 자식 엘리먼트를 조회하는 코드를 작성한다면
여러 번 반복해서 실행하는 코드가 필요

예시
다음 HTML 문서에서 id의 이름이 nav<div> 요소를 포함해서, 모든 자식 요소의 class 이름을 console.log를 사용하여 확인하는 방법은 무엇인지 의사코드로 작성해보자

<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>
⬇️  엘리먼트를 조회하는 코드를 작성하기 위한 수도코드
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은 document 객체에 구현되어 있음
브라우저에서 작동하는 모든 자바스크립트 코드에서는 document 객체를 조회할 수 있음

Document: 브라우저가 불러온 웹 페이지를 나타내며, DOM 트리의 진입점 역할



DOM 구조를 조회할 때는 console.log보다 console.dir이 유용함
console.dirconsole.log와 달리 DOM을 객체의 모습으로 출력함

⬆️  console.logconsole.dir 의 차이

body를 조회했을 때 나오는 수많은 속성들은
HTML 요소에 지정할 수 있었던 다양한 속성이 이미 객체 내에 존재한다고 보면 됨






💡 <body> 요소의 자식 요소 개수 알아보기

document.body 객체의 키 중에서 children을 찾으면 됨

⬆️  children 속성의 자식 요소까지 확인 가능

console.dir(document.body)를 통해 출력된 객체에서 children 속성을 찾을 수 있음
children 속성에 nav, news-contents, footer가 자식으로 있는 것도 확인 가능

물론 document.body.children으로 바로 조회할 수도 있음!






💡 idnews-contents<div> 요소의 부모 요소를 찾기

.parentElement 속성을 이용!

"id가 news-contents<div> 요소"는 <body> 요소의 자식 요소
반대로 <body> 요소는 "id가 news-contentsdiv 요소"의 부모 요소

id가 news-contents인 엘리먼트를 조회하려면,
document.body.children의 첫번째 요소를 조회해야 함

⬆️  document.body.children의 요소를 확인하고, id가 news-contents인 엘리먼트를 조회

이때, document.bodychildren을 조회할 때마다,
매번 document.body로부터 찾아가는 일은 매우 번거로운 일

하나의 변수를 선언해서 이 정보를 저장해두면 주소를 참조하기 때문에 언제든지 접근할 수 있음!

변수 newsContents를 선언하여 idnews-contents인 요소를 할당한다

let newsContents = document.body.children[1]

⬆️  document.body.children[1]를 변수 newsContents에 할당해주면 간단하게 접근 가능

newsContents의 부모 요소를 찾아보자!

document.body.children[1].parentElement
// or
newsContents.parentElement
//document.body.children[1]를 변수 newsContents에 할당해줬다면






📌  DOM 다루기

앞서 확인할 수 있듯이, document 객체에는 많은 속성과 메서드가 존재함

그러나 모든 속성과 메서드를 외워야 할 필요는 없으며
지금 가장 집중해야할 부분은 CRUD (Create, Read, Update and Delete)
CRUD를 먼저 이해하는 것이 새로운 언어를 가장 빠르게 학습하는 방법!

document 객체를 통해서 HTML 엘리먼트를 만들고(Create), 조회하고(Read), 갱신하고(Update), 삭체하는(Delete) 방법을 알아보자
* DOM에는 HTML에 적용하는(Append) 메서드가 따로 있으니 주의!






💡 CREATE: DOM을 이용하여 HTML Element를 추가해보자

새로운 <div> 요소 만들기

document.createElement('div')

⬆️  createElement 메서드를 사용하여 새로운 <div>를 만들어 줌

새롭게 생성한 div element를 활용하기 위해서는?
자바스크립트에서 어떤 작업의 결과를 담으려면 변수를 선언하고 작업의 결과를 변수에 할당해야함

변수 tweetDiv에 새롭게 생성한 <div> element를 할당한다

const tweetDiv = document.createElement('div')

아직은 화면에 아무런 변화가 없음
tweetDiv 라는 요소는 아무것도 연결되지 않은 하나의 노드인 상태

공중에 떠있는 엘리먼트를 웹페이지에서 확인하기 위해서는 APPEND 해야함
APPEND를 이용해 실제 웹페이지 상에도 보이는 것을 확인할 수 있음






💡 APPEND: DOM을 이용하여 HTML Element를 부모 노드에 포함해보자

append 메서드를 사용하여 포함할 수 있음

변수 tweetDiv에 담긴 새로운 <div>요소를 <body>요소에 append한다

document.body.append(tweetDiv)

그러나 아직 <div> 요소에 내용을 입력하지 않아서 보이는 내용이 없는 상태
크롬의 개발자 도구 Elements 탭에서 변경사항을 확인할 수 있음

⬆️  Elements 탭에서 <div> 요소가 <body>요소에 포함된 걸 확인할 수 있음

다른 트윗처럼 이쁘게 container 안에 넣기 위해서는?
생성된 tweetDivcontainer에 넣기 위해서는, container를 먼저 찾아야 함






💡 READ: DOM을 이용하여 HTML Element를 조회해보자

자바스크립트에서 원시 자료형인 변수의 값을 조회할 땐, 변수의 이름으로 직접 조회할 수 있으며
참조 자료형인 배열은 index를, 객체는 key를 이용해 값을 조회할 수 있음

DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는
querySelector의 첫번째 인자로 셀렉터(selector)를 전달하여 확인할 수 있음

셀렉터로는 HTML 요소(“div”), id(“#tweetList”), class(.tweet) 세 가지가 가장 많이 사용 됨
querySelector: 셀렉터를 조회한다는 의미. 'query'의 의미가 ‘질문하다’라는 것을 알고 있다면 역할을 쉽게 유추 가능!

'query' 라는 단어는 개발자 간에 “ㅇㅇㅇ를 조회한다” 라는 의미가
“쿼리를 날리다” 라는 jargon(특정 영역에서만 사용되는 단어)으로 굳어졌기 때문에 알고 있어야 함



querySelector‘.tweet’을 첫 번째 인자로 넣으면,
class 이름이 tweet인 HTML 엘리먼트 중 첫번째 엘리먼트를 조회할 수 있음

querySelector로 class 이름이 tweet인 HTML요소를 조회한다

const oneTweet = document.querySelector('.tweet')

HTML 문서에는 class 이름이 tweet인 요소가 여러 개 있는데,
변수 oneTweet에 할당된 요소는 단 하나
(중복인 경우 querySelector는 최상단 엘리먼트만을 조회함)

여러 개의 요소를 한 번에 가져오기 위해서는 querySelectorAll을 사용함

이렇게 조회한 HTML 요소들은 배열처럼 for문을 사용할 수 있음
주의!  앞서 조회한 HTML요소들은 배열이 아님

이런 ‘배열 아닌 배열’을 유사 배열, 배열형 객체 등 다양한 이름으로 부름
정식 명칭은 Array-like Object


querySelectorAll로 class 이름이 tweet인 모든 HTML 요소를 유사 배열로 받아온다

const tweets = document.querySelectorAll('.tweet')



querySelectorquerySelectorAll만 알아도 대부분의 요소를 조회할 수 있음
그러나 후에 개발자로 입사했을 때 get으로 시작하는 DOM 조회 메서드를 볼 수도 있음

get~ 메서드는 querySelector와 비슷한 역할을 하는 오래된 방식임
(인터넷 익스플로러 호환성을 고려했을 때 이런 방식을 사용)

실제 동작은 동일하니 이런 메서드가 있다는 것은 알아두기 !

getElementByIdquerySelector로 각각 받아 온 container 요소는 하나의 요소이다

const getOneTweet = document.getElementById('container')
const queryOneTweet = document.querySelector('#container')
console.log(getOneTweet === queryOneTweet) // true

⬆️  getElementByIdquerySelector로 받아 온 container 요소가 일치하는 모습



이로써 생성할 요소를 container에 넣을 준비를 마치게 됨
아래의 코드를 입력하면 container의 맨 마지막 자식 요소로 tweetDiv를 추가함


tweetDivcontainer의 마지막 자식 요소로 추가한다

const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)

⬆️  tweetDivcontainer의 마지막 자식 요소로 추가한 모습

현재 새롭게 추가된 tweetDiv는 별도의 class가 지정되어 있지 않아,
CSS를 이용한 스타일링이 적용되지 않음






💡 UPDATE: DOM을 이용하여 HTML Element를 변경해보자

기존에 생성한 빈 div 태그를 업데이트하여, 보다 다양한 작업을 할 수 있음

oneDiv 라는 이름의 <div> 요소를 만든다

const oneDiv = document.createElement('div');
console.log(oneDiv) // <div></div>

textContent를 이용해 비어있는 <div> 엘리먼트에 문자열을 입력한다

oneDiv.textContent = 'dev';
console.log(oneDiv) // <div>dev</div>

앞서 생성한 <div> 엘리먼트를 containerappend했을때
CSS 스타일링이 적용되지 않았음

CSS 스타일링이 적용될 수 있도록 <div> 엘리먼트에 class를 추가

classList.add를 이용해 tweet 클래스를 추가한다

oneDiv.classList.add('tweet')
console.log(oneDiv) // <div class="tweet">dev</div>



현재 생성한 엘리먼트에 텍스트를 채우고, 클래스를 추가하여 스타일링을 적용한 상태
이번에는 append를 이용해 container의 자식 요소로 추가

append를 이용해 container의 자식 요소에 oneDiv를 추가한다

const container = document.querySelector('#container')
container.append(oneDiv)

⬆️  oneDivtweet 클래스를 추가하여 스타일링한 후 container의 자식 요소로 추가한 모습

Q. class와 id말고 다른 attribute를 추가할 수는 없나요?
element.setAttribute(name, value) 를 사용하여 가능
지정된 요소의 속성 값을 설정
속성이 이미 있는 경우 값이 업데이트됨

// 예시
element.setAttribute('title', 'newTitle')`
: title 속성의 값을 newTitle로 설정

element.removeAttribute()
: 요소의 속성을 제거하는 메서드






💡 DELETE: DOM을 이용하여 HTML Element를 삭제해보자

1️⃣ 삭제하려는 요소의 위치를 알고 있는 경우
remove 메서드 사용

id가 container인 요소 아래에 tweetDiv를 추가하고, remove로 삭제한다

const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
tweetDiv.remove() // 이렇게 append 했던 요소를 삭제할 수 있다.

2️⃣ 여러 개의 자식 요소를 삭제하려는 경우

  1. innerHMTL을 이용
    해당 Element의 HTML, XML을 읽어오거나, 설정하는 Element의 속성
    (태그, 텍스트 모두 포함)

idcontainer인 요소 아래의 모든 요소를 지운다

document.querySelector('#container').innerHTML = '';

그러나 innerHTML은 몇 가지 보안 문제를 가지고 있음


  1. removeChild()을 이용
    removeChild는 자식 요소를 지정해서 삭제하는 메서드
    모든 자식 요소를 삭제하기 위해, 반복문(while, for, etc.)을 활용할 수 있음

container의 첫 번째 자식 요소가 존재하면, 첫번째 자식 요소를 제거한다

const container = document.querySelector('#container');
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

⬆️  removeChild 메서드와 while문을 활용하여 자식 요소를 삭제한 모습

removeChild와 while을 이용해 자식 요소를 삭제하면,
제목에 해당하는 H2 "Tweet List"까지 삭제됨

이를 방지하기 위한 방법은 다양함

  1️⃣ 자식 요소가 담고 있는 문자열을 비교해 "Tweet List"만 남긴다
  2️⃣ 새로운 변수에 "Tweet List"를 할당해뒀다가 반복문이 끝난 뒤에 새롭게 추가한다
  3️⃣ 자식 요소가 1개만 남을 때까지 마지막 자식 요소를 제거한다
  4️⃣ 직접 클래스 이름이 tweet인 요소만 찾아서 지운다

  1. container의 자식 요소가 1개만 남을 때까지, 마지막 자식 요소를 제거한다
const container = document.querySelector('#container');
while (container.children.length > 1) {
  container.removeChild(container.lastChild);
}

  1. 클래스 이름이 tweet인 요소만 찾아서 제거한다
const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
    tweet.remove();
})
// or
for (let tweet of tweets){
    tweet.remove()
}

⬆️  3, 4번 방법을 사용하면 제목 "Tweet List"를 남겨둘 수 있음






🖍 총 정리

새로운 요소 만들기
createElement(만들 요소)

document.createElement(‘div’)

요소를 DOM으로 조작하기 위해 변수에 할당

const tweetDiv = document.createElement('div')

요소 넣기
.append(넣을 요소)

document.body.append(tweetDiv)

요소 조회하기
.querySelector(요소/class/id)

const oneTweet = document.querySelector('.tweet')
// 클래스 tweet을 조회하여 변수에 할당

여러개의 요소를 한번에 조회하여 유사 배열로 받아오기
.querySelectorAll(요소/class/id)

const tweets = document.querySelectorAll('.tweet')

엘리먼트에 문자열을 입력
element.textContent = ‘문자열’

oneDiv.textContent = 'dev';

클래스 추가하기
추가받는el.classList.add(추가하려는 class)

oneDiv.classList.add('tweet')

요소의 속성 값 설정
element.setAttribute(name, value)

element.setAttribute('title', 'newTitle')
// title 속성의 값을 newTitle로 설정

위치를 알고 있는 요소 삭제하기
경로.remove()

const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
tweetDiv.remove() // 이렇게 append 했던 요소를 삭제할 수 있다.

모든 자식 요소 삭제
element.innerHTML = '' 활용

document.querySelector('#container').innerHTML = '';
// 클래스 container의 HTML태그를 ''로 만듦 = 지움

모든 자식 요소 삭제
parent.removeChild(child) 활용

const container = document.querySelector('#container');
while (container.firstChild) {
  container.removeChild(container.firstChild);
}
// 첫번째 자식 요소가 존재하면, 첫번째 자식 요소 제거

여러 개의 자식 요소 삭제
자식 요소가 1개 남을 때까지 마지막 자식 요소 제거

const container = document.querySelector('#container');
while (container.children.length > 1) {
  container.removeChild(container.lastChild);
}

여러 개의 자식 요소 삭제
클래스 이름에 해당하는 요소만 제거

const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
    tweet.remove();
})
// or
for (let tweet of tweets){
    tweet.remove()
}






📝 오늘의 느낀점

갑자기 엄청난 양의 낯선 개념들과... 낯선 메서드... 낯선 속성...
낯선 것들이 쏟아져서 허겁지겁 공부한 하루였다

DOM에 대한 개념이 이해하기 어려워서 구글링해서 나온 사이트 10개 정도 정독하고나니까 얼추 개념이 잡히는 것 같다. 해외 문서들을 번역하고, 정리해주신 수많은 블로거님들 정말 감사합니다..

그러나 이렇게 했는 데도 공부해야 할 개념들이 산더미다...
주말에 난리났다... 진자로
부트캠프 수강한 뒤로 주말이 주말이 아니다...😂

아무튼 어렵긴 했지만, 변화가 즉각적으로 눈에 보이는 작업을 하니 재밌었다!

오늘 DOM에 대해 학습하고 유효성 검사까지 실습했는데, 유효성 검사 내용까지 담기에는 페이지가 너무 길어질 것 같아 내일 내용에 한번에 적으려 한다

내일은 오늘 제작한 회원가입 페이지를 바탕으로 CSS 바꾼다
벌써 기대된다 내일도 화이팅 ~~

더 공부하기!
심화 학습 목표 보기
forEach()이거 뭔지






참고하면 좋은 사이트들 !!
브라우저는 어떻게 동작하는가?
https://d2.naver.com/helloworld/59361

DOM 쉽게 이해하기
https://kingofbackend.tistory.com/25






profile
안녕하세연🙋 프론트엔드 개발자입니다

0개의 댓글