DOM은 Document Object Model의 약자로, HTML 요소를 Object(JavaScript Object)처럼 조작(Manipulation)할 수 있는 Model입니다.
즉, 자바스크립트를 사용할 수 있으면, DOM으로 HTML을 조작할 수 있다!
그러면 DOM으로 HTML을 어떻게 조작할 수 있을까? 그것에 대한 궁금증에 대해서는 지금부터 알아보쟈!🙂
DOM을 JavaScript로 조작하여 HTML Element를 추가하거나 삭제, 혹은 내용을 변경할 수 있다.
즉, document 객체를 통해서 HTML 엘리먼트를 만들고(CREATE), 조회하고(READ), 갱신하고(UPDATE), 삭제(DELETE) 할 수 있다!
또한 DOM에는 HTML에 적용(APPEND)하는 메소드도 있어서 이 메소드를 통해 조작이 가능하다!
DOM으로 HTML을 조작하는 방법 중 가장 기초적인, 새로운 element를 만드는 방법에는 createElement
를 이용하는 방법이다!
document.createElement('div')
위의 Codepen예제를 통해서 새로운 element를 만들어보자!
아래의 사진과 같이 크롬 개발자 도구의 콘솔 탭에서 새로운 div 엘리먼트를 생성해보자!
새롭게 생성한 div element는 어떻게 활용할 수 있나요?
자바스크립트로 활용할 수 있어야한다! 그렇다면 자바스크립트에서 어떠한 결과를 담을 때에는! 변수를 선언하고 작업의 결과를 변수에 할당하면 된다.
여기에서는 div element를 변수 tweetDiv에 할당한다.
const tweetDiv = document.createElement('div')
이렇게 하고 나면 화면에는 아무런 반응이 일어나지 않는다... 그 이유는 tweetDiv
라는 요소는 현재 공중부양 중이기 때문이다!
그림과 같이 createElement
메소드로 생성된 엘리먼트는 공중에 떠있는 상태이다. 이 공중에 떠 있는 엘리먼트를 확인하기 위해서는 APPEND 해야한다!
CREATE에서 생성한 tweetDiv
를 append라는 메소드를 사용해서 body에 넣어보자!
document.body.append(tweetDiv)
<div></div>
가 생성되서 화면에 보일 줄 알았으나... 아무것도 나오지 않는다.. 그것에 대한 이유는 아무런 내용을 입력하지 않았기에 보이는 내용이 없는 것이다!
크롬의 개발자도구 Elements 탭에서 <div></div>
가 생성된 것을 확인 할 수 있다!
그렇다면 내용은 어떻게 넣는 걸까? 궁금증이 생길 것이다. UPDATE에서 textContent
를 이용하면 된다!
그 전에 tweetDiv
를 #container안에 마지막으로 위치하고 싶다면 어떻게 해야할까? READ의 querySelector
를 이용해서 container를 찾고 그 안에 위치시키면 된다!
자바스크립트에서 원시 자료형인 변수의 값을 조회하기 위해서는 변수의 이름으로 직접 조회할 수 있다. 참조 자료형인 배열은 index를, 객체는 key를 이용해 값을 조회할 수 있다.
여기서 막간을 이용하여! 원시자료형과 참조 자료형에 대해 이야기해보쟈!
- 원시자료형은 변경이 불가능한 값을 의미한다.
즉, 변수가 할당될 때 메모리 고정 크기로 값을 저장하고 해당 주소를 참조하는 특징을 가지고 있다. 그래서 불변성의 특징을 가지고 있다!- 원시자료형 6가지)
string
,number
,boolean
,undefined
,null
,symbol
- ex) let a = 10 ➡ a = 2 로 변경했을 때 새로운 메모리가 생성되고, 그것을 참조하는 주소만 변경되어진다!
- 참조자료형은 변경이 가능한 값을 의미한다.
- 대표적인 예시) 객체, 배열, 함수
- ex)
let a = [1,2,3,4]
의 배열을 만들고,
let b = a
b에 a 배열을 선언한 뒤
b[2] = 2
b에 대한 데이터를 바꾸면 a의 배열 값도 바뀌게 된다.
그러나 DOM은 위에 말했던 원시자료형과 참조자료형을 조회하는 방법과 달리 조금 특별한 방법을 사용해야한다!
DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는 querySelector
의 첫 번째 인자로 셀렉터(selector)를 전달하여 확인할 수 있다. 셀렉터로는 HTML 태그 ("div")
, id("#tweetList")
, class(.tweet)
이 세가지가 가장 많이 사용된다.
더 자세히 querySelect
에 대해 알아보쟈!
앞서 예시를 바탕으로 설명하자면,
querySelector
에 '.tweet'
을 첫 번째 인자로 넣으면, 클래스 이름이 tweet 인 HTML 엘리먼트 중 첫 번째 엘리먼트를 조회할 수 있다.
const oneTweet = document.querySelector('.tweet')
하지만 HTML 문서에는 클래스 이름이 tweet인 엘리먼트가 여러 개 있는데, 변수 oneTweet에 할당된 엘리먼트는 단 하나이다! 여러 개의 엘리먼트를 한 번에 가져오기 위해서는, querySelectorAll
을 사용해야한다. 이렇게 조회한 HTML 엘리먼트들은 배열처럼 for문을 사용할 수 있다!
const tweets = document.querySelectorAll('.tweet')
❗ 하지만...! 앞서 querySelectorAll
로 조회한 HTML엘리먼트들은 배열이 아니다! 즉, 배열이 아닌 배열, 유사배열 또는 배열형 객체(Array-like Object)이다!
const getOneTweet = document.getElementById('container')
const queryOneTweet = document.querySelector('#container')
console.log(getOneTweet === queryOneTweet) // true
querySelector
와 비슷한 DOM 조회 메소드에는 get
으로 시작하는 getElementById
가 있다. 이러한 메소드는 querySelector
와 비슷한 역할을 하는 오래된 방식이라고 기억해두면 좋다!
이제 다시! 문제로 돌아가서 CREATE에서 생성한 div
엘리먼트container
의 맨 마지막 자식 엘리먼트로 추가하는 코드를 작성해보자!
const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
이제 아래의 그림과 같이 container
에 잘 연결되는 것을 확인할 수 있다!
하지만 새롭게 추가된 tweetDiv
는 별도의 class 가 지정되어 있지 않아, CSS를 이용한 스타일링이 적용되지 않는다. 이제 tweetDiv
에 class를 붙여보자!
우선 그 전에 비어있는 div
엘리먼트에 textContent
를 사용해서 문자열을 입력해보자.
console.log(tweetDiv) // <div></div>
tweetDiv.textContent = 'dev';
console.log(tweetDiv) // <div>dev</div>
이제 CSS 스타일링이 적용될 수 있도록, div 엘리먼트에 class를 추가해보자.
tweetDiv.classList.add('tweet')
console.log(tweetDiv) // <div class="tweet">dev</div>
이제 성공적으로 추가된 것을 확인할 수 있다!
이제 CRUD의 Delete 즉, 삭제하는 방법에 대해서 알아보자.
const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
tweetDiv.remove() // 이렇게 append 했던 엘리먼트를 삭제할 수 있다.
만약 container에 있는 모든 내용을 삭제하려면 어떻게 해야할 까?
innerHTML
을 이용하면, 아주 간단하게 모든 자식 엘리먼트를 지울 수 있다!
document.querySelector('#container').innerHTML = '';
innerHTML
을 이용하는 방법은 분명 간편하고 편리한 방식이지만, 보안에서 몇 가지 문제를 가지고 있다.
이 방법을 대신할 다른 메소드 removeChild
은 자식 엘리먼트를 지정해서 삭제하는 메소드이다.
모든 자식 엘리먼트를 삭제하기 위해, 반복문 (while, for, etc.)
을 활용할 수 있다.
const container = document.querySelector('#container');
while (container.firstChild) {
container.removeChild(container.firstChild);
}
removeChild
과 while
을 이용해 자식 요소를 삭제하면, 제목에 해당하는 H2
"Tweet List"
까지 삭제되고야 만다. 이를 방지하기 위한 방법은 아래와 같다.
container의 자식 엘리먼트가 1개만 남을 때까지, 마지막 자식 엘리먼트를 제거하는 방법
const container = document.querySelector('#container');
while (container.children.length > 1) {
container.removeChild(container.lastChild);
}
클래스 이름이 tweet인 엘리먼트만 찾아서 제거하는 방법
const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
tweet.remove();
})
// or
for (let tweet of tweets){
tweet.remove()
}
이 외에도 삭제하는 방법에는 여러가지가 있다!
제일 처음으로 검색되는 div를 뱉거나, 만약 div가 없으면 null 을 리턴한다!
할 수 있다! querySelector 는 파워풀해서 복잡한 것도 찾기 때문이다.
아니다! 어떤 객체든 자식 엘리먼트를 가지고 있다면 querySelector의 부모가 될 수 있기 때문이다.
Node
는 태그 노드와 텍스트 노드 전체를 가리키고,Element
는 텍스트 노드를 제외하고,<a>
태그처럼 태그만 가리킨다. 그래서 태그만 검색하고 싶을 때에는 Element가 붙은 메소드를 선택해야한다. 즉,Node
는Element
보다 더 큰 범위라고 말한다.
ChildNodes
는 현재 요소의 자식 노드가 포함된 NodeList를 반환하여 태그노드와 텍스트 노드 모두 다 반환이되고,Children
은 HTMLCollection을 반환하고, 비요소는 모두 제외된다.
예시 링크
remove
는 노드를 메모리에서 삭제하고 종료한다. 하지만removechild
는 노드를 삭제하는 것이 아니라 메모리에 해당 노드는 존재하며, 부모-자식관계를 끊어서 DOM 트리에서 해제하는 것이다. 최종적으로는 관계를 끊은 해당 노드의 참조를 반환한다.
(노드리스트에서 배열 방법이 작동하지 않는 이유)
노드리스트는 배열이 아니기 때문입니다. 여기서
forEach
는 NodeList의 메소드이기도 해서 사용이 가능하지만,reduce
는 배열이 아닌 경우에는 사용이 불가능합니다.
Array.from()
Method// create a `NodeList` object const tweets = document.querySelectorAll('.tweet'); // convert `NodeList` to an array const tweetsArr = Array.from(tweets);
- Spread Operator -
[...iterable]
// create a `NodeList` object const tweets = document.querySelectorAll('.tweet'); // convert `NodeList` to an array const tweetsArr = [...tweets];
Array.prototype.slice()
Method// create a `NodeList` object const tweets = document.querySelectorAll('.tweet'); // convert `NodeList` to an array const tweetsArr = Array.prototype.slice.call(tweets); // or const tweetsArr = [].slice.call(tweets);