Part 4. DOM

HanSungUk·2022년 5월 17일
0

Javascript

목록 보기
10/16
post-thumbnail

Part 4. DOM 입니다.

현재 코드스테이츠 강의를 통해 프론트엔드를 학습하고 있습니다.
본 포스트는 해당 강의에 대한 내용 정리를 목적으로 합니다.

학습목표

  • DOM의 개념을 이해한다.
  • DOM의 구조를 파악하고, HTML과 DOM이 어떻게 닮아있는지 이해한다.
  • HTML에서 JavaScript 파일을 불러올 때 주의점에 대해서 이해한다.
  • DOM과 JavaScript의 차이에 대해 설명할 수 있다.

DOM은 Document Object Model(문서 객체 모델)의 약자입니다.
DOM은 문서의 구조화된 표현(structured representation)을 제공하며 프로그래밍 언어(ex. javascript)가 DOM 구조에 접근할 수 있는 방법(ex. 프로퍼티, 메서드)을 제공하여 그들이 문서 구조, 스타일, 내용 등을 변경할 수 있게 돕습니다.

DOMJavaScript의 관계에 대해 간략하게 설명하자면,
API(web of XML page) = DOM + JS(scripting language)과 같은 방정식으로 표현할 수 있습니다.

즉, DOM은 프로그래밍 언어가 아니며 웹 페이지라는 일종의 문서(document)를 node로 표현하는 Model입니다. 여기서 DOM에서 부르는 node란 객체를 의미합니다.
JavaScript는 문서(document)와 문서의 요소(element)에 접근하기 위해 DOM을 사용합니다.
DOM이 없다면 JavaScript는 웹페이지 또는 XML 페이지 및 요소들과 관련된 모델이나 개념들에 대한 정보를 갖지 못합니다.

브라우저의 최상단에는 window라는 root객체가 있습니다. window객체는 2가지 역할을 합니다.

  • 자바스크립트 코드의 전역 객체입니다.
  • '브라우저 창(browser window)'을 대변하고, 이를 제어할 수 있는 메서드를 제공합니다.

BOM은 Browser Object Model(브라우저 객체 모델)의 약자입니다. 문서 이외의 모든 요소를 제어하기 위해 브라우저(호스트 환경)가 제공하는 추가 객체를 나타냅니다.

1. DOM 기초

웹 브라우저의 렌더링 엔진은 작성된 코드를 해석하는 과정(HTML parsing)에서 <script>요소를 만나면, 웹 브라우저는 HTML 해석을 잠시 멈춥니다. HTML 해석을 잠시 멈춘(HTML parsing paused) 웹 브라우저는 <script>요소를 먼저 실행합니다.

렌더링 엔진이 HTML, CSS를 파싱한 결과물로 웹 페이지를 브라우저에 표시합니다. 렌더링 엔진은 우리가 흔히 알고 있는 크롬, 사파리의 webkit, 파이어폭스의 Gecko 등이 있습니다.

파싱(parsing)의 의미는 문서의 내용을 토큰(token)으로 분석하고, 문법적 의미와 구조를 반영한 파스트리(parse tree)를 생성하는 과정입니다. 토큰(token)이란 구문적으로 의미를 갖는 최소의 단위이며, 우리가 작성하는 프로그램들은 모두 토큰으로 이뤄집니다.

다음은 렌더링 엔진의 동작 과정과 크롬과 사파리 렌더링 엔진인 웹킷(webkit) 동작 과정을 나타냅니다.

웹킷(webkit) 동작 과정

위 동작들은 점진적으로 진행됩니다. 렌더링 엔진은 좀 더 나은 사용자 경험을 위해 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작합니다.

하지만 <script>요소의 위치에 따라 html 파싱이 중단되는 경우가 발생합니다.

  1. <head> 요소 내부에 <script> 태그 적용
    <script> 요소는 다운로드(fetching)과 실행(execution) 과정을 거치는데 렌더링 엔진이 HTML을 파싱할 때 <script> 요소를 만나면 <script>요소를 먼저 해석하고 HTML을 파싱하기 때문에 사용자는 <script>요소가 해석되는 동안 빈 화면을 보고 있어야 합니다. 또한 만일 <script> 요소에서 DOM 요소를 조작한다면 아직 HTML 파싱이 완전히 이뤄지지 않았기 때문에 코드 에러가 발생할 수 있습니다.

  2. <body>요소 내부에 <script> 요소 적용
    html을 파싱한 후 <script> 요소를 적용하기 때문에 위에 언급된 문제가 발생하지 않습니다. 하지만 웹 페이지가 자바스크립트에 의존적이라면 사용자는 <script> 요소가 다운되고 실행될 때까지 정적인 웹 페이지를 보고 있어야 합니다.

따라서, 다음과 같이 defer 속성을 적용한 <script> 요소 사용을 권장합니다.

<html lang="en">
<head>
  <script defer src="script.js"></script>
</head>
<body>
  <h1>오옷!</h1>
</body>
</html>  

defer는 HTML을 파싱하다 <script> 요소를 만나면 HTML을 파싱하면서 <script> 요소는 다운로드만 받습니다.
<script> 요소는 HTML 파싱이 끝난 후 실행시킵니다.

  • 정적인 웹 페이지에 접근하여 동적으로 웹페이지를 변경하기 위한 유일한 방법은 메모리 상에 존재하는 DOM을 변경하는 것입니다.
    이때 필요한 것이 DOM에 접근하고 변경하는 프로퍼티와 메소드의 집합인 DOM API이고 자바스크립트에서 활용할 수 있습니다. 즉, 페이지 콘텐츠는 DOM에 저장되고 자바스크립트를 통해 접근하거나 조작할 수 있습니다.
    API(web or XML page) = DOM + JS(scripting language)

  • 브라우저가 HTML 문서를 로드한 후 파싱하여 생성한 모델을 DOM Tree라고 합니다. 아래 이미지와 같이 객체의 트리로 구조화되어 있기 때문에 DOM Tree라고 부릅니다.
    모든 HTML 요소와 요소의 어트리뷰트, 텍스트를 각각의 객체로 만들고 이들 관계를 부모-자식 관계로 표현한 구조입니다.

DOM에서는 <html>, <body>, <head>, <title> 와 같이 모든 HTML 태그를 객체로 나타내고, DOM은 자바스크립트를 통해 동적으로 변경될 수 있습니다.
위에서도 잠깐 언급했지만 DOM에서는 이 객체들은 노드(node)라고 표현합니다.
즉, 태그는 요소 노드(element node)이고 요소 내의 문자는 텍스트 노드(text node)가 됩니다.

DOM에서 모든 요소, 어트리뷰트, 텍스트는 하나의 객체이며 Document 객체의 자식입니다. 따라서 DOM tree의 진입점(entry point)은 document 객체이며, 최종점은 요소의 텍스트를 나타내는 객체입니다.

실무에서는 주로 네 가지의 노드를 다룬다고 합니다.
1. DOM의 '진입점'이 되는 문서(document) 노드. document객체를 의미합니다.
2. HTML 태그에서 만들어지며, DOM 트리를 구성하는 블록인 요소 노드(element node)
3. 텍스트를 포함하는 텍스트 노드(text node) 요소 노드의 자식이며 자신의 자식 노드를 가질 수 없습니다. DOM tree의 최종단입니다.
4. 화면에는 보이지는 않지만, 정보를 기록하고 자바스크립트를 사용해 이 정보를 DOM으로부터 읽을 수 있는 주석(comment) 노드

2. DOM 다루기

학습 목표

  • DOM을 JavaScript로 조작하여 HTML Element를 추가할 수 있다.(CREATE)
  • DOM을 JavaScript로 조작하여 HTML Element를 조회할 수 있다.(READ)
  • DOM을 JavaScript로 조작하여 HTML Element를 변경할 수 있다.(UPDATE)
  • DOM을 JavaScript로 조작하여 HTML Element를 삭제할 수 있다.(DELETE)
  • 생성한 HTML Element를 부모 엘리먼트로 포함할 수 있다.(APPEND)
  • innerHTMLtextContent의 차이를 이해한다.

2-1 CREATE

Document.createElement() 메서드는 지정한 tagName의 HTML 요소를 만들어 반환합니다.

let element = document.createElement(tagName[, options]);

CREATE에서 생성한 tweetDiv를 트리 구조와 연결하기 위해서 append라는 메서드를 사용할 수 있습니다.

const tweetDiv = document.createElement('div')

2-2. APPEND

  • element.append()메서드는 노드 객체(Node Object)DOMString(text)element의 자식 노드로 추가할 수 있습니다.
append(param1)
append(param1, param2)
append(param1, param2, /* ... ,*/ paramN)
const tweetDiv = document.createElement('div')
document.body.append(tweetDiv)

console.log(document.body.append(tweetDiv)) // undefined
// 이렇게 문자열도 삽입 가능합니다.
document.tweetDiv.append('append 예시')

appnd 메서드는 return 값을 반환하지 않습니다.

  • element.appendChild() 메서드는 append 메서드와는 다르게 오직 노드 객체(Node Object)만 추가할 수 있습니다. 게다가 append는 여러 개의 노드와 문자를 추가할 수 있는 반면에 appendChild메서드는 한 번에 오직 하나의 노드만 추가할 수 있습니다.
    하지만 appendChild 메서드는 return 값을 반환합니다.
const tweetDiv = document.createElement('div')
document.body.append(tweetDiv)

console.log(document.body.append(tweetDiv)) // <div></div>

2-3. READ

DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는 querySelector의 첫 번째 인자로 셀렉터(selector)를 전달하여 확인할 수 있습니다.
셀렉터로는 HTML 요소, id, class 세 가지가 가장 많이 사용됩니다.

querySelector는 셀렉터를 조회한다는 의미를 가지고 있습니다. 이 query라는 단어는 개발자 간에 "ㅇㅇㅇ를 조회한다"라는 의미를 "쿼리를 날리다"라는 jargon(특정 영역에서만 사용되는 단어)로 굳어졌습니다.

  • querySelector.tweet을 첫 번째 인자로 넣으면, 클래스 이름이 tweet HTML 엘리먼트 중 첫 번째 엘리먼트 를 조회할 수 있습니다.
const oneTweet = document.querySelector('.tweet')

만일 HTML 문서에서 클래스 이름이 tweet 인 여러 개의 요소를 한 번에 가져오고 싶다면 querySelectorAll를 사용할 수 있습니다.
querySelectorAll메서드는 조회한 HTML 요소들을 유사 배열(Array-like Object)로 받아옵니다.

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

2-4. UPDATE

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

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

  • textContent 메서드는 요소의 문자열을 조회하거나 삽입할 수 있습니다.
oneDiv.textContent = 'dev'
console.log(oneDiv) // <div>dev</div>
  • classList.add 메서드는 요소에 class를 추가할 수 있습니다. 참고로 classList.remove로 삭제할 수도 있습니다.
oneDiv.classList.add('tweet')
console.log(oneDiv) // <div class="tweet">dev</div>
  • setAttribute 메서드는 요소에 속성을 추가할 수 있습니다.
setAttribute(name, value)
oneDiv.setAttribute("name", "hello oneDiv")
console.log(oneDiv) // <div class="tweet" name= "hello oneDiv">dev</div>

2-5. DELETE

  • removeChild는 자식 요소를 지정해서 삭제하는 메서드 입니다. 모든 자식 요소를 삭제하기 위해, 반복문(while, for 등)을 활용할 수 있습니다. 다음의 코드는 자식 요소가 남아있지 않을 때까지 첫 번째 자식 요소를 삭제하는 코드입니다.
const container = document.querySelector('#container');
while (container.firstChild) {
  container.removeChild(container.firstChild);
}

이렇게 자식 요소를 하나만 남기고 삭제하는 코드를 작성할 수도 있습니다.

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

또는 remove메서드를 활용해서 클래스 이름이 tweet인 요소만 찾아서 지우는 방법도 있습니다.

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

0개의 댓글