DOM은 Document Object Model의 약자로, HTML 요소를 Object(JavaScript Object)처럼 조작(Manipulation)할 수 있는 Model입니다.
즉 JavaScript를 사용할 수 있으면, DOM으로 HTML을 조작할 수 있습니다.
HTML을 조작할 수 있다는 게 무슨 뜻일까요? 여러 뛰어난 웹 개발자들이 모여 HTML을 분석하여 HTML의 아주 작은 부분까지 접근할 수 있는 구조를 만들었습니다.
이렇게 만들어진 구조를 DOM이라고 합니다. DOM을 이용하면 HTML로 구성된 웹 페이지를 동적으로 움직이게 만들 수 있습니다. 앞서 학습한 조건문과 반복문, 배열, 객체를 활용하면 소셜 미디어에서 새롭게 생성되는 게시물을 저장하고 분류하는 작업을 구현할 수 있습니다.
HTML에 JavaScript를 적용하기 위해서는 script 태그를 이용합니다. 아래의 경우 HTML 파일과 같은 디렉토리에 존재하는 myScriptFile.js을 불러옵니다.
<script src="myScriptFile.js"></script>
웹 브라우저가 작성된 코드를 해석하는 과정에서 script 요소를 만나면, 웹 브라우저는 HTML 해석을 잠시 멈춥니다. HTML 해석을 잠시 멈춘 웹 브라우저는 script 요소를 먼저 실행합니다. script 요소는 등장과 함께 실행된다는 사실을 꼭 기억하세요
이 script 요소를 추가하는 두 가지 대표적인 사례가 존재합니다.
하나는 head 요소에 추가하는 방법, 다른 하나는 body 가 끝나기 전에 추가하는 방법입니다. 두 사례를 비교해 보고 차이점은 무엇인지 알아봅시다.
<!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>
<!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>
두 방식 모두 myScriptFile.js 내의 첫 번째 console.log를 성공적으로 출력하지만,
두 번째 console.log의 경우 제대로 출력하지 못하는 예시가 있습니다.
console.log('welcome JavaScript');
let msgElement = document.querySelector('#msg');
console.log(msgElement);
document 객체에는 많은 속성과 메서드가 존재합니다. 모든 속성과 메서드를 외워야 할까요? 지금 당장 전부 알아야 할 필요는 없습니다. 지금 집중할 부분은 CRUD(Create, Read, Update and Delete)입니다.
document.createElement('div')
새롭게 생성한 div element는 어떻게 활용할 수 있나요?
const tweetDiv = document.createElement('div')
아직 화면에 변화가 없는 게 당연합니다. tweetDiv라는 요소는 현재 공중부양 중입니다. 다음 그림을 보며 설명합니다. DOM의 구조를 나타내는 트리 구조를 하나 그립니다. 아무것도 연결이 되어있지 않은 하나의 노드를 그립니다.
위 그림처럼 공중에 떠있는 엘리먼트를 확인하기 위해서는 APPEND 해야 합니다. APPEND를 이용해 실제 웹 페이지 상에도 보이는 것을 확인할 수 있습니다. 이어지는 콘텐츠 APPEND에서, tweetDiv를 트리 구조에 연결합니다.
REATE에서 만든 tweetDiv라는 변수는 아직 "공중부양"을 하고 있습니다.
이번 콘텐츠에서는 append라는 메서드를 사용해서, 변수 tweetDiv를 body에 넣어보겠습니다.
document.body.append(tweetDiv)
아무것도 보이지 않는데, 잘못 입력한 건가요?
아니요. 잘 적용된 겁니다. 새롭게 생성한 div 요소에 아무런 내용을 입력하지 않아서 보이는 내용이 없을 뿐입니다. 크롬의 개발자 도구 Elements 탭에서 내용을 확인하세요.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Document</title>
</head>
<body>
<div id="container">
<h2>Tweet List</h2>
<div class="tweet">hello</div>
<div class="tweet">world</div>
<div class="tweet">code</div>
<div class="tweet">states</div>
</div>
<div></div>
</body>
</html>
내용은 어떻게 넣나요?
다음 콘텐츠인 UPDATE에서 좀 더 자세히 설명합니다. 이번 콘텐츠에서는 textContent라는 메서드를 활용하면 문자열을 입력할 수 있습니다.
다른 트윗처럼 이쁘게 container 안에 넣을 순 없나요?
생성한 tweetDiv를 container에 넣기 위해서는, container를 먼저 찾아야 합니다. 어떻게 container를 찾을 수 있을까요? 위에서 언급했던 DOM 트리를 순회해서 찾을 수 있습니다. 그러나 보다 더 편리한 방법이 있으니 검색해 보시기 바랍니다.
JavaScript에서 원시 자료형인 변수의 값을 조회하기 위해서는, 변수의 이름으로 직접 조회할 수 있습니다.
참조 자료형인 배열은 index를, 객체는 key를 이용해 값을 조회할 수 있습니다.
그러나 DOM은 조금 특별한 방법을 사용해야 합니다. DOM으로 HTML 엘리먼트의 정보를 조회하기 위해서는 querySelector의 첫 번째 인자로 선택자(selector)를 전달하여 확인할 수 있습니다.
선택자로는 HTML 요소("div"), id("#tweetList"), class(.tweet) 세 가지가 가장 많이 사용됩니다.
참고로 querySelector는 선택자를 조회한다는 의미를 가지고 있습니다.
query의 의미가 "질문하다"라는 것을 알고 있다면 역할을 쉽게 유추하실 수 있습니다.
이 query라는 단어는 개발자 간에 "ㅇㅇㅇ를 조회한다"라는 의미를 "쿼리를 날리다"라는 jargon(특정 영역에서만 사용되는 단어)로 굳어졌기 때문에 알고 있어야 합니다.
querySelector에 '.tweet'을 첫 번째 인자로 넣으면, 클래스 이름이 tweet 인 HTML 엘리먼트 중 첫 번째 엘리먼트를 조회할 수 있습니다.
const tweets = document.querySelectorAll('.tweet')
HTML 문서에는 클래스 이름이 tweet 인 요소가 여러 개 있는 데, 변수 oneTweet에 할당된 요소는 단 하나입니다. 여러 개의 요소를 한 번에 가져오기 위해서는,
querySelectorAll을 사용합니다.
이렇게 조회한 HTML 요소들은 배열처럼 for문을 사용하실 수 있습니다. 주의하세요! 앞서 조회한 HTML 요소들은 배열이 아닙니다! 이런 '배열 아닌 배열'을 유사 배열, 배열형 객체 등 다양한 이름으로 부릅니다. 정식 명칭은 Array-like Object입니다. Array-like Object 같이 개념을 설명하는 용어는 영어로도 명확하게 기억해 두는 게 좋습니다.
const tweets = document.querySelectorAll('.tweet')
querySelector와 querySelectorAll만 알아도 대부분의 요소를 조회할 수 있습니다. 다만, 여러분이 어떤 회사에 개발자로 입사한 다음, 아래와 같이 get으로 시작하는 DOM 조회 메서드를 볼 수도 있습니다.
이런 메서드는 querySelector와 비슷한 역할을 하는 오래된 방식이라고만 이해하면 됩니다. 만약 이전 버전의 브라우저(인터넷 익스플로러) 호환성을 신경 써야 한다면, 이런 옛날 방식을 사용해야 할 수도 있습니다. 실제 동작은 동일하니 이런 메서드가 있다는 것 정도는 알아두세요.
const getOneTweet = document.getElementById('container')
const queryOneTweet = document.querySelector('#container')
console.log(getOneTweet === queryOneTweet) // true
CREATE에서 생성한 div 요소를 container에 넣을 준비를 마쳤습니다. 다음 코드를 입력하면, container의 맨 마지막 자식 요소로 tweetDiv를 추가합니다.
const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
새롭게 추가된 tweetDiv는 별도의 class가 지정되어 있지 않아, CSS를 이용한 스타일링이 적용되지 않습니다. 이어지는 콘텐츠에서는, 새롭게 생성한 tweetDiv에 class를 붙여 스타일링을 적용합니다.
앞선 콘텐츠 CREATE, APPEND, READ를 통해 새로운 DOM 객체를 만들고, 기존의 DOM 객체에 붙이고, DOM 객체를 선택해서 조회하는 방법을 학습했습니다. oneDiv라는 이름의 div 요소를 만들어 보겠습니다.
const oneDiv = document.createElement('div');
console.log(oneDiv) // <div></div>
이번 콘텐츠 UPDATE에서는 기존에 생성한 빈 div 태그를 업데이트하여, 보다 다양한 작업을 할 수 있습니다.
먼저, textContent를 사용해서, 비어있는 div 엘리먼트에 문자열을 입력합니다.
oneDiv.textContent = 'dev';
console.log(oneDiv) // <div>dev</div>
앞서 생성한 div 엘리먼트를 container에 append 했을 때, CSS 스타일링이 적용되지 않았습니다.
CSS 스타일링이 적용될 수 있도록, div 엘리먼트에 class를 추가합니다.
oneDiv.classList.add('tweet')
console.log(oneDiv) // <div class="tweet">dev</div>
생성한 엘리먼트에 텍스트를 채웠고, 클래스를 추가하여 스타일링을 적용했습니다. 이번에는 append를 이용해 container의 자식 요소로 추가합니다.
const container = document.querySelector('#container')
container.append(oneDiv)
CRUD의 Delete, 삭제하는 법을 학습합니다.
삭제하는 방법에도 여러 가지가 있습니다.
먼저 삭제하려는 요소의 위치를 알고 있는 경우에 사용하는 방법입니다. 앞서 생성하고 추가한 tweetDiv를 삭제합니다. remove 메서드를 사용하세요.
const container = document.querySelector('#container')
const oneDiv = document.createElement('div')
container.append(oneDiv)
oneDiv.remove() // 이렇게 append 했던 요소를 삭제할 수 있다.
여러 개의 자식 요소를 지우려면, 어떻게 해야 할까요?
innerHTML을 이용하면, 아주 간단하게 모든 자식 요소를 지울 수 있습니다. 컨테이너의 모든 자식 요소를 지우려면, 다음과 같이 입력합니다
document.querySelector('#container').innerHTML = '';
innerHTML을 이용하는 방법은 분명 간편하고 편리한 방식이지만, innerHTML은 보안에서 몇 가지 문제를 가지고 있습니다. 이 방법을 대신할 다른 메서드를 사용합니다.
removeChild는 자식 요소를 지정해서 삭제하는 메서드입니다. 모든 자식 요소를 삭제하기 위해, 반복문(while, for, etc.)을 활용할 수 있습니다. 다음의 코드는 자식 요소가 남아있지 않을 때까지, 첫 번째 자식 요소를 삭제하는 코드입니다.
const container = document.querySelector('#container');
while (container.firstChild) {
container.removeChild(container.firstChild);
}
removeChild와 while을 이용해 자식 요소를 삭제하면, 제목에 해당하는 H2 "Tweet List"까지 삭제됩니다. 이를 방지하기 위한 방법은 여러 가지가 있습니다.
자식 요소가 담고 있는 문자열을 비교해 "Tweet List"만 남기거나, 새로운 변수를 생성하고 Tweet List를 할당해 뒀다가 반복문이 끝난 뒤에 새롭게 추가할 수도 있습니다. 또는 자식 요소를 하나만 남기게 할 수도 있습니다.
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()
}