210426_DOM

Bitnara Lee·2021년 4월 26일
0

DOM

Document Object Model의 약자 : HTML 요소를 Object(JavaScript Object)처럼 조작(Manipulation)할 수 있는 Model

-> 자바스크립트에서 DOM은 document 객체에 구현되어 있다. 브라우저에서 작동되는 자바스크립트 코드에서는, 어디에서나 document 객체를 조회, HTML 접근 가능하다.(document.querySelector~~)

DOM과 JavaScript의 차이 :

W3C의 DOM 스펙에선, 문서의 의미는 HTML에만 국한된것이 아니라고 한다. XML 역시 DOM을 사용하여 데이터를 관리할 수 있으며, DOM은 다양한 환경과 어플리케이션에서 사용할 수 있는 API라고 설명한다.

HTML은 단순히 규칙에 따라 정해진 태그, 속성값으로 이루어진 언어이며,
DOM은 브라우저가 HTML 파싱한 후 생성되는 객체 모델로, document에 접근가능한 API이다.
참고블로그

node : The name of any type of object in the DOM tree. It could be one of the built-in DOM elements such as the document itself, document.head or document.body. A node could be an HTML tag (input, div, h2, p) or it could be a comment node, text node… a node is any DOM object and every node has a parent, every node is allowed to have one or more children or even zero children.

element : An element is a specific type of node, one that can be directly specified in the HTML with an HTML tag and can have properties like an id or a class. can have children, etc.

HTML에 자바스크립트 적용하기


웹 브라우저가 작성된 코드를 해석하는 과정에서 <script> 만나면 HTML 해석을 잠시 멈추고 <script> 먼저 실행
script 요소는 등장과 함께 실행(웹 브라우저가 가장 먼저 실행)

<head> 태그에 추가 : 브라우저가 아직 document.body를 읽지 않았기 때문에 <head> 안에 있는 스크립트에선 document.body에 접근하지 못함
</body> 가 끝나기 전에 추가 : 나은 방법

<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>

DOM 구조를 조회할 때에는 console.dir 이 유용

Q1 - 자식 엘리먼트 찾기

console.dir(document.body) 를 통해 출력된 객체에서, children 속성을 찾을 수 있습니다.

children(child node) :
document.body.children 으로 바로 조회할 수도 있습니다.

->바로 아래의 자식 요소. 자식 노드는 부모 노드의 바로 아래에서 중첩 관계를 만듬

firstChild/ firstElementChild : 첫 번째 자식 요소 노드
lastChild/ lastElementChild : 마지막 자식 요소 노드
childNodes 컬렉션 : 텍스트 노드를 포함한 모든 자식 노드.
배열이 아닌 반복 가능한(iterable, 이터러블) 유사 배열 객체인 컬렉션(collection) -> for ..of 사용가능

element: node에 속함
text: node이나 !element

Q2 - 부모 엘리먼트 찾기

id의 이름이 news-contents 인 div 엘리먼트의 부모 엘리먼트는?


id가 news-contents 인 엘리먼트를 조회하려면, document.body.children 의 첫 번째 엘리먼트를 조회 -> document.body.children[1]
매번 찾아가기 번거로움 -> 변수 newsContents 를 따로 선언하여 id가 news-contents 인 엘리먼트를 할당

parentNode : (종류에 상관 없이)부모노드 조회 (ex) document.body.parentNode)
parentElement : 부모 '요소 노드' 조회, 요소 노드가 아닌 경우 'null'

탐색 프로퍼티를 사용하면 이웃 노드로 바로 이동가능.

모든 노드에 적용 가능 : parentNode, childNodes, firstChild, lastChild, previousSibling, nextSibling
요소 노드에만 적용 가능 : parentElement, children, firstElementChild, lastElementChild, previousElementSibling, nextElementSibling

Element.closest()
var closestElement = targetElement.closest(selectors);

DOM의 구조

트리 구조 : 부모가 자식을 여러 개 가지고, 부모가 하나인 구조가 반복됨

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

DOM으로 HTML 조작

CRUD(Create, Read, Update and Delete)

Achievement Goals
DOM을 JavaScript로 조작하여 HTML Element를 추가하거나 삭제, 혹은 내용을 변경할 수 있다.
createElement - CREATE
querySelector, querySelectorAll - READ
textContent, id, classList, setAttribute - UPDATE
remove, removeChild, innerHTML = "" , textContent = "" - DELETE
appendChild - APPEND
innerHTML과 textContent의 차이

Advanced Challenge

HTML5 template tag 사용법을 이해할 수 있다.

children과 childNodes의 차이를 이해할 수 있다.
remove와 removeChild의 차이를 이해할 수 있다.
같은 엘리먼트를 appendChild 하면, 기존 엘리먼트를 복사할까?
좌표 정보 조회를 할 수 있다. - offsetTop...
크기 정보 조회를 할 수 있다. - offsetWidth...

CREATE - createElement

자바스크립트에서 어떤 작업의 결과를 담으려면:
변수를 선언하고 어떤 작업의 결과를 변수에 할당.

const tweetDiv = document.createElement('div')
-> div element를 변수 tweetDiv 에 할당 (아직 공중부양)

APPEND - append, appendChild

document.body.append(tweetDiv)
->변수 tweetDiv에 담긴 새로운 div 엘리먼트를 body 엘리먼트에 append

생성한 tweetDiv 를 container 에 넣기 위해 container 찾는 방법

  • 위에서 언급했던 DOM 트리 순회

  • 더 편리한 방법

*자식을 찾는다 (변수에 담고) -> 자식.parentNode로 조회가능 (변수에 담을 수도)
--------------------------------------------------------------------

*자식 찾기
var first = parent.childNodes;   // 모든 자식들 찾아 변수에 담기
var first = parent.firstChild;   // 첫번째 자식 찾아 변수에 담기
var last = parent.lastChild;    // 마지막 자깃 찾아 변수에 
*그외 Sibling, .parentNode.parentNode(조부모)

*부모자식관계
$(A).append(B) : 위에서 언급한 메소드이다. 부모 A태그 가장 뒤에 B태그를 위치시킴
$(A).prepend(B) : 부모 A태그 가장 앞에 B태그를 위치시킴
$(A).appendTo(B) : to로 인해 헷갈릴 수 있는 메서드다 주의하자. A태그를 부모 B태그 가장 뒤에 위치시킴
$(A).prependTo(B) : A태그를 부모 B태그 가장 앞에 위치시킴
 
*형제관계
$(A).before(B) : A객체 앞에 B를 위치시킴
$(A).after(B) : A객체 뒤에 B를 위치시킴
$(A).insertBefore(B) : A객체를 B앞에 위치시킴
$(A).insertAfter(B) : A객체를 B뒤에 위치시킴

appendChild() :appends a node as the last child of a node.
오직 Node object만 받음, 한번에 하나의 노드만 추가 가능, DOMString(text) 사용불가능(error), return값 반환

append(): Node object, DOMString(text) 사용가능, 한번에 여러 자식요소 설정, return값 반환 x(console.log(document.body.append(something~))// undefined)

같은 엘리먼트를 appendChild 하면 -> 오류 VM5618:1 Uncaught DOMException: Failed to execute 'appendChild' on 'Node': The new child element contains the parent.
at :1:3

documentFragment:

라이브 DOM 트리 외부에 경량화된 DOM 객체를 만들 수 있다.
마치 라이브 DOM 트리 처럼 동작하나, 메모리상에서만 존재하는 빈 문서이다.
appendChild()insertBefore()와 같은 Node 인터페이스 메서드를 사용하여 DOM에 삽입하는 것입니다. 이렇게 하면 DocumentFragment의 노드들이 DOM으로 이동되고 빈 DocumentFragment만 남게 된다.->모든 노드 한번에 문서에 삽입됨

//HTML//
<ul id="list"></ul>
  
---------------------------------------------------
//JavaScript//
var list = document.querySelector('#list')
var fruits = ['Apple', 'Orange', 'Banana', 'Melon']

var fragment = new DocumentFragment()

fruits.forEach(function (fruit) {
  var li = document.createElement('li')
  li.innerHTML = fruit
  fragment.appendChild(li)
})

list.appendChild(fragment)
--------------------------------------------------
Apple
Orange
Banana
Melon

READ - querySelector, querySelectorAll

DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는 querySelector의 첫 번째 인자로 셀렉터(Selector)를 전달하여 확인
참고로 querySelector는 셀렉터를 조회한다는 의미
셀렉터로는 HTML 태그("div"), id("#tweetList"), class(.tweet) 세 가지가 가장 많이 사용됩

const oneTweet = document.querySelector('.tweet')   //  querySelector로 클래스 이름이 tweet인 HTML 엘리먼트를 조회

const tweets = document.querySelectorAll('.tweet')  // querySelectorAll로 클래스 이름이 tweet 인 모든 HTML 엘리먼트를 유사 배열로 받아옵니다.

querySelectorAll 이렇게 조회한 HTML 엘리먼트들은 배열처럼 for문을 사용하실 수 있습니다. but not 배열!
이런 '배열 아닌 배열'을 유사 배열, 배열형 객체 등 다양한 이름으로 부릅니다. -> 정식 명칭 Array-like Object
Array-like Object

원하는 것 조회하고 싶을때 : 맨 왼쪽 버튼 클릭하고 $0 이용

get으로 시작하는 DOM 조회 메소드(오래된 방식)
const getOneTweet = document.getElementById('container')
const queryOneTweet = document.querySelector('#container')
console.log(getOneTweet === queryOneTweet) // true

_Futher Study
MDN에서 querySelector를 검색하여, 다음의 질문에 대해 학습합니다.

querySelector의 첫번째 인자에 'div'를 넣으면 어떻게 될까요?
querySelector를 통해서 더 복잡한 작업을 할 수 있을까요?
querySelector의 부모는 꼭 document 객체여야만 할까요?_

UPDATE - textContent, classList.add

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

_Further Study
아래 키워드에 대해서 직접 검색하여 공부해보시기 바랍니다.

textContent와 innerHTML의 차이 (difference between textContent and innerHTML)
innerHTML의 보안상 단점 (mdn innerHTML security issue)_

DELETE - remove, removeChild

tweetDiv.remove() // 이렇게 append 했던 엘리먼트를 삭제할 수 있다
innerHTML :
document.querySelector('#container').innerHTML = '';
[코드] id가 container인 엘리먼트 아래의 모든 엘리먼트를 지웁니다.

innerHTML은 보안에서 몇 가지 문제를 가지고 있습니다

removeChild

자식 엘리먼트를 지정해서 삭제하는 메소드. 모든 자식 엘리먼트를 삭제하기 위해, 반복문(while, for, etc.)을 활용가능.

while (container.firstChild) {
  container.removeChild(container.firstChild);
} 
// container의 첫 번째 자식 엘리먼트가 존재하면, 첫 번째 자식 엘리먼트를 제거합니다. -> 모든 자식 삭제하게됨

removeChild 와 while 을 이용해 자식 요소를 삭제하면, 제목에 해당하는 H2 "Tweet List"까지 삭제됩니다. 이를 방지하기 위한 방법은 여러 가지가 있습니다. 자식 요소가 담고 있는 문자열을 비교해 "Tweet List"만 남기거나, 새로운 변수를 생성하고 Tweet List를 할당해뒀다가 반복문이 끝난 뒤에 새롭게 추가할 수도 있습니다. 또는 자식 엘리먼트를 하나만 남기게 할 수도 있습니다.
const container = document.querySelector('#container');
while (container.children.length > 1) {
container.removeChild(container.lastChild);
}
[코드] container의 자식 엘리먼트가 1개만 남을 때까지, 마지막 자식 엘리먼트를 제거합니다.

또는 직접 클래스 이름이 tweet인 엘리먼트만 찾아서 지우는 방법도 있습니다.

const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
tweet.remove();
})
// or
for (let tweet of tweets){
tweet.remove()
}
[코드] 클래스 이름이 tweet인 엘리먼트만 찾아서 제거합니다.

_Further Study
아래 키워드에 대해서 직접 검색하여 학습하세요.

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)_

배열이 아닌 반복 가능한(iterable, 이터러블) 유사 배열 객체인 컬렉션(collection)

  • 컬렉션이기 때문에 for .. of 사용 가능
    • 이터러블이기 때문에 Symbol.iterator 프로퍼티 구현되 있기 때문
  • 배열이 아니기 때문에 배열 메서드를 쓸 수 없다.(위 답변)
  • Array.from()을 사용하면 진짜 배열을 만들 수 있다.

Array.from(): static method creates a new, shallow-copied Array instance from an array-like or iterable object.얕은 복사된 새 배열 인스턴스를 생성

DOM유용블로그

profile
Creative Developer

0개의 댓글