참조 deepdive
DOM(Document Object Model)
✔️HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 즉 프로퍼티와 메서드를 제공하는 트리 자료구조다
트리 자료구조(tree data structure)
✔️노드들의 계층 구조
✔️부모 노드와 자식 노드로 구성되어 노드 간의 계층적 구조를 표현
✔️비선형 자료구조 : 하나의 자료 뒤에 여러 개의 자료가 존재할 수 있는 자료구조 (ex. 트리, 그래프)
<->선형 자료구조 : 하나의 자료 뒤에 하나의 자료만 존재하는 자료구조(ex. 배열, 스택, 큐, 링크드 리스트, 해시 테이블)
✔️하나의 최상위 노드에서 시작 -> 루트 노드 -> 부모 노드X
✔️리프 노드 : 자식 노드가 없는 노드
중요한 노트 타입 4가지
✔️문서 노드 : 최상위에 존재하는 루트 노드, document 객체를 가리킴
✔️요소 노드 : HTML 요소를 가리키는 객체, 문서의 구조를 표현 (ex. div, li, ul ...)
✔️어트리뷰트 노드 : HTML 요소의 어트리뷰트를 가리키는 객체 (ex. id, class, src ...)
✔️텍스트 노드 : HTML 요소의 텍스트를 가리키는 객체, 리프노드, DOM 트리의 최종단
요소 노드 취득
✔️getElementById
-> id 값을 갖는 첫 번째 요소 노드만 반환
-> 요소가 존재하지 않는 경우 null을 반환
✔️getElementsByTagName
-> 태그 이름을 갖는 모든 요소 노드 반환
-> 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환
✔️getElementsByClassName
-> class 값을 갖는 모든 요소 노드 반환
-> 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환
✔️querySelector
-> 하나의 요소 노드들 반환(첫 번째 요소 노드)
-> 요소가 존재하지 않는 경우 null을 반환
✔️querySelectorAll
-> 모든 요소 노드 반환
자식 노드 탐색
자식 노드 존재 확인
✔️hasChildNodes()
-> 결과값 true or false
-> 텍스트 노드를 포함하여 자식 노드의 존재를 확인
✔️children.length, childElementCount
-> 텍스트 노드가 아닌 요소 노드가 존재하는지 확인
부모 노드 탐색
✔️parentNode
-> 부모 노드가 텍스트 노드인 경우는 없음
형제 노드 탐색
노드 정보 취득
요소 노드의 텍스트 조작
✔️nodeValue
->setter와 getter 모두 존재하는 접근자 프로퍼티
->참조와 할당 모두 가능
->텍스트 노드의 값(=텍스트) 반환
->이 외의 노드는 null 반환
->값을 할당하면 텍스트 변경 가능
✔️textContent
->요소 노드의 텍스트와 모든 자손 노드의 텍스트를 모두 취득하거나 변경
->childNodes 프로퍼티가 반환한 모든 노드들의 텍스트 노드 반환
->nodeValue보다 간단하게 코드 작성 가능
✔️innerText
->textContent와 유사한 동작을 하지만 사용 지양
->크로스 사이트 스크립팅 공격에 취약
✔️inserAdjacentHTML
->기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입
->'beforebegin', 'afterbegin', 'beforeend', 'afterend'
->크로스 사이트 스크립팅 공격에 취약
노드 생성과 추가
앞 블로그에 다뤘던 내용이라 간단하게 정리
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//요소 노드 생성
const $li = document.createElement('li');
//텍스트 노드 생성
const textNode = document.createTextNode('Banana');
//텍스트 노드를 $li 요소 노드의 자식 노드로 추가
//$li.textContent = 'Banana';와 동일
$li.appendChild(textNode);
//$li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
</script>
복수의 노드 생성과 추가
//1
//DOM이 3번 변경된다
//비효율적
<body>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
['Apple', 'Banana', 'Orange'].foreach(text => {
//요소 노드 생성
const $li = document.createElement('li');
//텍스트 노드 생성
const textNode = document.createTextNode(text);
//텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
//$li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
});
</script>
//2
//DOM이 1번만 변경
//불필요한 컨테이너 요소가 DOM에 추가되는 부작용
<body>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//컨테이너 요소 노드 생성
const $container = document.createElement('div');
['Apple', 'Banana', 'Orange'].foreach(text => {
//요소 노드 생성
const $li = document.createElement('li');
//텍스트 노드 생성
const textNode = document.createTextNode(text);
//텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
//$li 요소 노드를 컨테이너 요소의 마지막 자식 노드로 추가
$container.appendChild($li);
});
//컨테이너 요소 노드를 $fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($container);
</script>
위에 두 가지 방식은 비효율 적인 방식
✔️DocumentFragment
->효율적으로 사용할 수 있음
<body>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//DocumentFragment 노드 생성
const $fragment = document.createDocumentFragment();
['Apple', 'Banana', 'Orange'].foreach(text => {
//요소 노드 생성
const $li = document.createElement('li');
//텍스트 노드 생성
const textNode = document.createTextNode(text);
//텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
//$li 요소 노드를 컨테이너 요소의 마지막 자식 노드로 추가
$fragment.appendChild($li);
});
//컨테이너 요소 노드를 $fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($fragment);
</script>
지정한 위치에 노드 삽입
✔️inserBefore(newNode, childNode)
->첫 번째 인수로 전달받은 노드를 두 번째 인수로 전달받은 노드 앞에 삽입
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//요소 노드 생성
const $li = document.createElement('li');
//텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(document.createTextNode('Orange'));
//$li 요소 노드를 $fruits 요소 노드의 마지막 자식 요소 앞에 삽입
$fruits.insertBefore($li, $fruits.lastElementChild);
// Apple - Orange - Banana
</script>
->두 번째 인수로 전달받은 노드가 null이면 appendChild 메서드처럼 동작
노드 이동
✔️appendChild 또는 insertBefore 메서드를 사용
->현재 위치에서 노드를 제거하고 새로운 위치에 노드를 추가 ->즉, 노드가 이동
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//이미 존재하는 요소 노드를 취득
const [$apple, $banana, ] = $fruits.children;
//이미 존재하는 $apple 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
#fruits.appendChild($apple); // Banana - Orange - Apple
//이미 존재하는 $banana 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
$fruits.insertBefore($banana, $fruits.lastElementChild);
// Orange - Banana - Apple
</script>
노드 복사
✔️cloneNode([deep: true | false])
->노드의 사본을 생성
->true일 때, 깊은 복사로 자손 노드가 포함된 사본을 생성
->false일 때, 얕은 복사로 자손 노드를 복사하지 않음(텍스트 노드도 없음)
노드 교체
✔️replaceChild(newChild, oldChild)
->자신을 호출한 노드의 자식 노드를 다른 노드로 교체
->oldChild 노드는 DOM에서 제거
<body>
<ul id="fruits">
<li>Apple</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//기존 노드와 교체할 요소 노드를 생성
const $newChild = document.createElement('li');
$newChild.textContent = 'Banana';
// $fruits 요소 노드의 첫 번째 자식 요소 노드를 $newChild 요소 노드로 교체
$fruits.replaceChild($newChild, $fruits.firstElementChild);
// Banana
</script>
노드 삭제
✔️removeChild(child)
->child 매개변수에 인수로 전달한 노드를 DOM에서 삭제
<body>
<ul id="fruits">
<li>Apple</li>
<li>Banana</li>
</ul>
</body>
<script>
const $fruits = document.getElementById('fruits');
//#fruits 요소 노드의 마지막 요소를 DOM에서 삭제
$fruits.removeChild($fruits.lastElementChild);
// Apple
</script>
HTML 어트리뷰트 조작
✔️attribute 프로퍼티는 getter만 존재하는 읽기 전용 접근자 프로퍼티
->어트리뷰트 값을 취득할 수 있지만 변경불가
✔️getAttribute / setAttribute
->attribute 프로퍼티를 통하지 않고 직접 값을 취득하거나 변경할 수 있음
->getAttribute : 값을 참조
->setAttribute : 값을 변경
<body>
<input id="user" type="text" value="ungmo2">
<script>
const $input = document.getElementById('user');
//value 어트리뷰트 값을 취득
const inputValue = $input.getAttribute('value');
console.log(inputValue); //ungmo2
//value 어트리뷰트 값을 변경
$input.setAttribute('value', 'foo');
console.log($input.getAttribute('value')); //foo
</script>
</body>
✔️hasAttribute(attributeName)
->어트리뷰트 존재여부 확인
✔️removeAttribute(attributeName)
->어트리뷰트 삭제
<body>
<input id="user" type="text" value="ungmo2">
<script>
const $input = document.getElementById('user');
//value 어트리뷰트 존재 확인
if($input.hasAttribute('value')){
//value 어트리뷰트 삭제
$input.removeAttribute('value');
}
console.log($input.hasAttribute('value')); //false
</script>
</body>
HTML 어트리뷰트 VS DOM 프로퍼티
✔️요소 노드 객체에는 HTML 어트리뷰트HTML 어트리뷰트에 대응하는 프로퍼티(DOM 프로퍼티)가 존재
✔️DOM 프로퍼티들은 HTML 어트리뷰트 값을 초기값으로 가지고 있음
✔️요소 노드는 2개의 상태를 관리 ->초기 상태, 최신 상태
✔️HTML 어트리뷰트
->HTML 요소의 초기 상태를 지정
✔️DOM 프로퍼티
->최신 상태를 유지
->DOM 프로퍼티에 값을 할당 -> 최신 상태 값을 변경하는 의미
HTML 어트리뷰트와 DOM 프로퍼티의 대응 관계
- id 어트리뷰트와 id 프로퍼티는 1:1 대응, 동일한 값으로 연동
- input 요소의 value 어트리뷰트는 value 프로퍼티와 1:1 대응, 하지만 value 어트리뷰트는 초기 상태, value 프로퍼티는 최신 상태를 가짐
- class 어트리뷰트는 className, classList 프로퍼티와 대응
- for 어트리뷰트는 htmlFor 프로퍼티와 1:1 대응
- td 요소의 colspan 어트리뷰트는 대응하는 프로퍼티가 존재X
- textContent 프로퍼티는 대응하는 어트리뷰트가 존재X
- 어트리뷰트 이름은 대소문자를 구별하지 않지만 대응하는 프로퍼티 키는 카멜 케이스를 따름(maxlength -> maxLength)
data 어트리뷰트와 dataset 프로퍼티
->HTML 요소에 정의한 정의 어트리뷰트와 자바스크립트 간에 데이터를 교환할 수 있음
->data 어트리뷰트는 data-uer-id, data-role과 같이 data- 접두사 다음에 임의의 이름을 붙여 사용
✔️dataset 프로퍼티
->data 어트리뷰트 값 취득
✔️data 어트리뷰트
->data- 접두사 다음에 존재하지 않는 이름을 키로 사용하여 dataset 프로퍼티에 값을 할당하면 data 어트리뷰트가 추가
<!DOCTYPE html>
<html>
<body>
<ul class="users">
<li id="1" data-user-id="7621" data-role="admin">Lee</li>
<li id="1" data-user-id="9524" data-role="subscriber">Kim</li>
</ul>
<script>
const users = [...document.querySelector('.users').children];
//user-id가 '7621'인 요소 노드를 취득
const user = users.find(user => user.dataset.userId === '7621');
//user-id가 '7621'인 요소 노드에서 data-role의 값을 취득
console.log(user.dataset.role); //admin
//user-id가 '7621'인 요소 노드의 data-role의 값을 변경
user.dataset.role = 'subscriber';
//dataset프로퍼티는 DOMStringMap 객체를 반환
console.log(user.dataset); //DOMStringMap {userId : '7621', role: 'subscriber'}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<ul class="users">
<li id="1" data-user-id="7621">Lee</li>
<li id="1" data-user-id="9524">Kim</li>
</ul>
<script>
const users = [...document.querySelector('.users').children];
//user-id가 '7621'인 요소 노드를 취득
const user = users.find(user => user.dataset.userId === '7621');
//user-id가 '7621'인 요소 노드에 새로운 data 어트리뷰트를 추가
user.dataset.role = 'admin';
console.log(user.dataset);
/*
DOMStringMap {userId : '7621', role: 'admin'}
-> <li id="1" data-user-id="7621" data-role="admin">Lee</li>
*/
</script>
</body>
</html>
인라인 스타일 조작
✔️style 프로퍼티
->인라인 스타일을 취득하거나 추가 또는 변경
<!DOCTYPE html>
<html>
<body>
<div style="color: red;">Hello World</div>
<script>
const $div = document.querySelector('div');
//인라인 스타일 취득
console.log($div.style); //CSSStyleDeclaration { 0: "color", ...}
//인라인 스타일 변경
$div.style.color = 'blue';
//인라인 스타일 추가
$div.style.width = '100px';
$div.style.height = '100px';
$div.style.backgroundColor = 'yellow';
</script>
</body>
</html>
클래스 조작
✔️className
->어트리뷰트 값을 취득하거나 변경
->어트리뷰트 값을 문자열로 반환
<!DOCTYPE html>
<html>
<head>
<style>
.box{
width: 100px; height: 100px;
background-color: antiquewhite;
}
.red{ color: red;}
.blue{ color: blue;}
</style>
</head>
<body>
<div class="box red">Hello World</div>
<script>
const $box = document.querySelector('box');
//.box 요소의 class 어트리뷰트 값을 취득
console.log($box.className); // 'box red'
//.box 요소의 class 어트리뷰트 값 중에서 'red'만 'blue'로 변경
$box.className = $box.className.replace('red', 'blue');
</script>
</body>
</html>
✔️classList
->어트리뷰트의 정보를 담은 DOMTokenList 객체를 반환
<!DOCTYPE html>
<html>
<head>
<style>
.box{
width: 100px; height: 100px;
background-color: antiquewhite;
}
.red{ color: red;}
.blue{ color: blue;}
</style>
</head>
<body>
<div class="box red">Hello World</div>
<script>
const $box = document.querySelector('box');
//.box 요소의 class 어트리뷰트 정보를 담은 DOMTokenList 객체를 취득
// classList가 반환하는 DOMTokenList 객체는 HTMLCollection과 NodeList와 같이
// 노드 객체의 상태 변화를 실시간으로 반영하는 살아 있는(live) 객체다.
console.log($box.classList);
// DOMTokenList(2) [length: 2, value: 'box blue', 0: 'box', 1: 'blue']
//.box 요소의 class 어트리뷰트 값 중에서 'red'만 'blue'로 변경
$box.classList = $box.className.replace('red', 'blue');
</script>
</body>
</html>
✔️add(...className)
->인수로 전달한 1개 이상의 문자열을 class 어트리뷰트 값으로 추가
$box.classList.add('foo'); //-> class="box red foo"
$box.classList.add('bar', 'baz'); //-> class="box red foo bar baz"
✔️remove(...className)
->인수로 전달한 1개 이상의 문자열과 일치하는 클래스를 class 어트리뷰트에서 삭제
->없을 시 에러 없이 무시
$box.classList.remove('foo'); //-> class="box red bar baz"
$box.classList.remove('bar', 'baz'); //-> class="box red"
$box.classList.remove('x'); //-> class="box red"
✔️item(index)
->index에 해당하는 클래스를 class 어트리뷰트에서 반환
-> ex. index가 0이면 첫 번째 클래스를 반환
$box.classList.item(0); //'box'
$box.classList.item(1); //'red'
✔️contains(className)
->포함되어 있는지 확인
$box.classList.contains('box'); //true
$box.classList.contains('blue'); //false
✔️replace(oldClassName, newClassName)
->첫 번째 인수로 전달한 문자열을 두 번째 인수로 전달한 문자열로 변경
$box.classList.replace('red', 'blue'); //class='box blue'
✔️toggle(className[.force])
->일치하는 클래스가 존재하면 제거, 존재하지 않으면 추가
$box.classList.toggle('foo'); //class="box blue foo"
$box.classList.toggle('foo'); //class="box blue"
->두 번째 인수로 불리언 값으로 평가되는 조건식을 전달할 수 있다.
->true면 강제로 추가
->false면 강제로 제거
✔️이 외에도 forEach, entries, keys, values, supports메서드를 제공
요소에 적용되어 있는 CSS 스타일 참조
->style 프로퍼티는 인라인 스타일만 반환
✔️getComputedStyle
->HTML 요소에 적용되어 있는 모든 CSS 스타일을 참조
<!DOCTYPE html>
<html>
<head>
<style>
body{ color: red;}
.box{
width: 100px; height: 50px;
background-color: cornsilk;
border: 1px solid black;
}
</style>
</head>
<body>
<div class="box">Box</div>
<script>
const $box = document.querySelector('box');
//.box 요소의 적용된 모든 CSS 스타일을 담고 있는 CSSStyleDeclaration 객체를 취득
const computedStyle = window.getComputedStyle($box);
console.log(computedStyle); //CSSStyleDeclaration
//임베딩 스타일
console.log(computedStyle.width); //100px
console.log(computedStyle.height); //50px
console.log(computedStyle.backgroundColor); //rgb(255, 248, 220)
console.log(computedStyle.border); //1px solid rgb(0, 0, 0)
//상속 스타일
console.log(computedStyle.color); //rgb(255, 0, 0)
//기본 스타일
console.log(computedStyle.display); //block
</script>
</body>
</html>
->두 번째 인자로 :after, :before 같은 의사 요소를 지정하는 문자열 전달 가능
->의사 요소 아닌 것은 두 번째 인수 생략
<!DOCTYPE html>
<html>
<head>
<style>
.box::before{
content: 'Hello';
}
</style>
</head>
<body>
<div class="box">Box</div>
<script>
const $box = document.querySelector('box');
//의사 요소 :before의 스타일을 취득
const computedStyle = window.getComputedStyle($box, ':before')
console.log(computedStyle.content); //'Hello'
</script>
</body>
</html>
'CodeStates > JavaScript' 카테고리의 다른 글
나만의 아고라 스테이츠 만들기 (0) | 2023.05.05 |
---|---|
Section1 / Unit11 : Coz’ Mini Hackathon (0) | 2023.05.04 |
Section1 / Unit10 : [JS / 브라우저] DOM (0) | 2023.05.01 |
Section1 / Unit9 : JavaScript Koans (0) | 2023.04.28 |
Section1 / Unit9 : ES6주요문법 (0) | 2023.04.28 |