🍁Axis
태그를 찾는 방법으로 식별자를 이용해서 찾는 방법이 있고, 축을 기준으로 찾는 axis 기능이 있다.
기본 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
border: 1px solid black;
padding: 5px;
margin: 5px;
}
#me {
border: 5px solid cornflowerblue;
}
.check {
outline: 5px solid coral;
}
</style>
</head>
<body>
<h1>Axis</h1>
<input type="button" value="버튼" id="btn1">
<hr>
<div>
할아버지
<div>
큰아버지
<div>사촌</div>
</div>
<div>
아버지
<div>큰형</div>
<div>작은형</div>
<div id="me">나
<div>자식
<div>손자</div>
</div>
<div>자식</div>
<div>자식</div>
</div>
<div>동생</div>
<div>막내동생</div>
</div>
<div>
작은아버지
<div>사촌</div>
</div>
</div>
</body>
</html>
전형적인 계층의 모습을 <div> 태그를 이용해 구현해 보았다.
그리고 누가 누구의 자식인지를 알 수 있게 <style> 속성을 수정했다.
🍁트리 구조
BOM 트리 구조
BOM 계층의 트리 구조는 위와 같다.
🍂DOM 트리 구조
DOM 트리 구조는 태그들로 구성된 트리가 아니며, 문서 내의 모든 요소가 노드로 구성된다.
DOM의 꼭대기에는 Document가 있고, Document에 자식이 있다. DOM은 문서의 실제 태그의 최고 꼭대기에 있는 <html> 태그가 가장 상위의 태그이고, 그다음이 <body> 태그이다. 그리고 문서 내의 모든 요소가 노드로 구성되므로 아래 코드에서의 <body> 태그의 자식에는 <div>와 <a> 태그가 있는 셈이다.
문서 내의 모든 요소가 노드로 구성된다.
공백, PCDATA와 속성도 족보에 포함이 된다. 따라서 <div> 태그 아래에는 <a> 태그가 있고, <a> 태그 아래에는 text와 속성까지 계층에 나타나게 된다.
주의할 점은 공백(스페이스, 탭)도 데이터라는 점이다. <body> 태그 바로 아래에 <h1> 태그가 자식으로 올 것 같지만, 그다음으로 오는 것은 공백이고, 그다음으로 <h1> 태그가 온다는 점이다. 이처럼 눈에 보이는 모든 것이 노드로 계층에 포함되지만, 눈에 보이지 않는 것도 포함이 될 수 있으므로 주의하도록 한다.
DOM에서는 개행(Enter)도 데이터로 잡기 때문에 디테일하게 데이터를 관리할 수 있지만, 이와 같이 주의가 필요하다.
DOM 트리의 구성 요소(노드)
- 태그, PCDATA, 주석, 엔티티, 선언문, 속성, 등..
1. 태그(1)
2. 속성(2)
3. PCDATA(3)
4. 주석(8)
5. 선언문(13)
정리를 하자면 DOM 트리의 구성 요소에는 태그 이외의 것들도 포함된다.
뒤의 숫자는 고유 식별자이다. 각자의 프로퍼티가 있기 때문에 어떤 타입인지를 반환받은 위 숫자를 보고 태그인지, 속성인지, 문자열인지를 알아볼 수 있다.
뒤의 숫자는 아래에서 설명할 DOM 노드 프로퍼티의 nodeType에 해당하며, 다른 건 몰라도 이중 1, 2, 3은 꼭 기억하도록 한다.
DOM 노드 프로퍼티
1. nodeType: 해당 노드가 어떤 형식인지를 나타낸다. (열거형(숫자))
for (var i=0; i<me.childNodes.length; i++) {
console.log(me.childNodes[i]);
console.log(me.childNodes[i].nodeType);
}
"나 "는 PCDATA이기 때문에 3, <div>는 태그이기 때문에 1이 출력되는 것을 확인할 수 있다.
2. nodeName: 해당 노드의 이름
- 태그: 태그명 반환
- 속성: 속성명 반환
- PCDATA: #text 반환
for (var i=0; i<me.childNodes.length; i++) {
console.log(me.childNodes[i]);
console.log(me.childNodes[i].nodeName);
}
nodeName 프로퍼티는 태그, 속성, PCDATA에 따라 다른 값을 반환한다.
3. nodeValue: 해당 노드의 값
- 태그: 무조건 null 반환
- 속성: 속성 값 반환
- PCDATA: 문자열 반환
for (var i=0; i<me.childNodes.length; i++) {
console.log(me.childNodes[i]);
console.log(me.childNodes[i].nodeValue);
}
태그는 값이 없기 때문에 nodeValue로 무조건 null로 반환한다.
🍁DOM 노드(태그) 검색
1. 식별자 검색 도구
- document.getElementById('id');
- document.getElementsByClassName('class');
- document.getElementsByName('name');
- document.getElementsByTagName('tag');
- document.querySelector('CSS selector');
- document.querySelectorALL('CSS selector');
2. 트리 구조 기반 (상대 표현(Axis))
- 나를 기준으로 부모, 자식, 형제를 탐색하는 방법
두 가지 방법 중 한 가지만 사용하지 않고, 혼합해서 사용한다.
나를 중심으로 찾아보도록 하자.
body 찾기
document.getElementById('btn1').onclick = m1;
function m1() {
var me = document.getElementById('me');
document.body.className = 'check';
}
class라는 속성이 Java에서 이미 존재하기 때문에 JavaScript에서는 class 대신에 className이라고 한다.
태그에 동적으로 .check 클래스를 적용하여 배경선을 주는 방법을 이용해서 자식 태그를 찾는 과정을 알아보도록 하자.
자식 태그 찾기
me.childNodes
- me.childNodes: 자식 노드들
- me.firstcChild: 첫 번째 자식 (me.childNodes[0])
- me.lastChild: 막내 자식 (me.childNodes[length-1])
me.children
- me.children: 모든 태그 자식들
- me.firstElementChild: 첫 번째 자식 태그
- me.lastElementChild: 막내 자식 태그
for (var i=0; i<me.childNodes.length; i++) {
console.log(me.childNodes[i]);
}
나를 기준으로 했을 때 자식 태그를 확인할 수 있다.
<div> 태그 찾기
me.childNodes으로 찾기
for (var i=0; i<me.childNodes.length; i++) {
if (me.childNodes[i].nodeType == 1 && me.childNodes[i].nodeName == 'DIV') {
me.childNodes[i].className = 'check';
}
}
me.children으로 찾기
for (var i=0; i<me.children.length; i++) {
me.children[i].className = 'check';
}
Axis를 이용하면 위와 같이 자식 노드 중에서도 <div> 태그만을 찾을 수 있다.
children으로 찾아도 결과는 같기 때문에 childNodes보다 children으로 많이 찾는 편이다.
부모 or 조상 찾기
- me.parentNode
- me.parentElement
아버지
for (var i=0; i<me.children.length; i++) {
me.parentNode.className = 'check';
}
할아버지
for (var i=0; i<me.children.length; i++) {
me.parentElement.parentElement.className = 'check';
}
증조할아버지
for (var i=0; i<me.children.length; i++) {
me.parentElement.parentElement.parentElement.className = 'check';
}
<html> 태그가 가장 꼭대기에 있지만, 그 위에 document 객체가 있다.
document는 태그가 아니므로 document에 접근하려면 parentNode로 접근해야 한다.
태그일 경우에만 parentElement로 접근한다.
for (var i=0; i<me.children.length; i++) {
alert(me.parentElement.parentElement.parentElement.parentElement.parentNode == document);
}
document에 접근하여 document가 같은지를 물어보자 true를 출력하는 것을 볼 수 있다.
document 위의 window는 BOM에서는 인식이 되지만, DOM에서는 인식이 안 된다. 물론 이렇게까지 올라가는 경우는 거의 없다.
큰아버지
for (var i=0; i<me.children.length; i++) {
me.parentElement.parentElement.firstElementChild.className = 'check';
}
큰아버지 사촌
for (var i=0; i<me.children.length; i++) {
me.parentElement.parentElement.firstElementChild.firstElementChild.className = 'check';
}
형제간 찾기
- me.previousSibling: 바로 위의 형제 (모든 노드)
- me.nextSibling: 바로 밑의 형제 (모든 노드)
- me.previousElementSibling : 바로 위의 형제 (태그)
- me.nextElementSibling : 바로 밑의 형제 (태그)
자바스크립트에서는 형제간 접근 프로퍼티도 지원한다.
작은형
for (var i=0; i<me.children.length; i++) {
me.previousElementSibling.className = 'check';
}
큰형
for (var i=0; i<me.children.length; i++) {
me.previousElementSibling.previousElementSibling.className = 'check';
}
막내동생
for (var i=0; i<me.children.length; i++) {
me.nextElementSibling.nextElementSibling.className = 'check';
}
원래는 형제간에 접근을 하려면 부모까지 올라갔다가 내려와야 하는데, 자바스크립트는 이를 미리 프로퍼티로 만들어 두었다.
HTML에서 id와 class를 남발하여 사용하면 가독성이 저하되고 코드 관리가 열악해지므로, 최소한의 id와 class를 사용하면서 태그를 검색할 수 있도록 이와 같이 상대 검색을 지원한다.
🍁Axis의 활용
테이블 조작
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.table {
border: 1px solid black;
border-collapse: collapse;
}
.table td {
border: 1px solid black;
padding: 20px;
text-align: center;
font-size: 1.5rem;
}
</style>
</head>
<body oncontextmenu="return false;" onselectstart="return false;">
<h1>테이블</h1>
<table class="table" id="tbl1">
<!-- tr*5>td{0}*5 -->
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</table>
<h1>또 다른 테이블</h1>
<table class="table">
<!-- tr*3>td{item}*3 -->
<tr>
<td>item</td>
<td>item</td>
<td>item</td>
</tr>
<tr>
<td>item</td>
<td>item</td>
<td>item</td>
</tr>
<tr>
<td>item</td>
<td>item</td>
<td>item</td>
</tr>
</table>
<script>
var tbl1 = document.getElementById('tbl1');
//테이블의 자식인 <tbody>의 자식(tr)을 찾음
var tr = tbl1.firstElementChild.children;
//tr의 자식(td)을 찾음
for (var i=0; i<tr.length; i++) { //행
var td = tr[i].children;
for (var j=0; j<td.length; j++) { //열
td[j].onmouseover = m1;
td[j].onmouseout = m2;
td[j].onmousedown = m3;
}
}
function m1() {
event.target.bgColor = 'gold';
}
function m2() {
event.target.bgColor = 'transparent';
}
function m3() {
var n = parseInt(event.target.textContent); //형변환 (문자>숫자)
if (event.buttons == 1) {
n++;
} else if (event.buttons == 2) {
n--;
} else if (event.buttons == 4) {
n=0;
}
event.target.textContent = n;
}
</script>
</body>
</html>
테이블의 자식으로 바로 아래에 <tbody>가 있기 때문에 테이블의 자식을 찍으면 1이 출력된다.
<tr>의 자식이 <tbody>이다. 이를 찾기 위해 tbl1.firstElementChild.children를 찾아 tr 변수에 저장하였다.
그리고 tr의 children을 찾아 td에 저장하고, 마우스를 올렸을 때 색상이 변경되는 이벤트를 설정했다.
onselectstart="return false;"로 설정하면 드래그로 잡히지 않게 된다. 만약 테이블만 블럭 잡히지 않게 하려면 이를 테이블 태그에 붙이면 된다.
CSS 선택자 사용
var list2 = document.querySelectorAll('#tbl2 td');
for (var i=0; i<list2.length; i++) {
list2[i].bgColor = 'cornflowerblue';
}
요즘에는 위와 같이 자식의 자식을 찾지 않고 querySelectorAll이라는 기능을 사용한다.
CSS 선택자를 사용하면 훨씬 짧은 로직으로 태그를 찾는 게 가능해진다.
물론 CSS 선택자가 만능은 아니므로 하나의 방법만을 사용하지 말고 모두 사용할 수 있어야 한다.