DOM 조작 및 탐색
| 프로퍼티 | 설명 |
| childNodes | 자식노드를 모두 탐색하여 NodeList에 담아 반환. 요소 노드 + 텍스트 노드 |
| children | 자식 노드 중 요소 노드만 모두 탐색하여 HTMLColleciton객체에 담아 반환 (텍스트 노드X) |
| firstChild | 첫 번째 자식 노드를 반환 |
| lastChild | 마지막 자식 노드를 반환 |
| firstElementChild | 첫 번째 자식 요소 노드를 반환 |
| lastElementChild | 마지막 자식 요소 노드를 반환 |
| hasChildNodes() | 자식 요소를 가지고 있는지 확인 |
| parentNode | 부모노드를 모두 탐색하여 NodeList로 반환 |
| previousSibling | 같은 부모를 가진 형제 노드 중에서 자신의 이전 형제 노드를 반환 |
| nextSibling | 같은 부모를 가진 형제 노드 중에서 자신의 다음 형제 노드를 반환 |
| previousElementSibling | 같은 부모를 가진 형제 노드 중에서 자신의 이전 형제 요소 노드를 반환 |
| nextElementSibling | 같은 부모를 가진 형제 노드 중에서 자신의 다음 형제 요소 노드를 반환 |
ChildNodes vs Children
- childNodes는 줄을 맞추면 7개(공백 기준), 한 줄로 나열하면 3개로 달라지므로 예측하기 불편
- children도 arraylike 이기 때문에 배열로 가져오면 배열고차함수도 쓸 수 있어서 더 편리

firstElementChild vs firstChild
- firstChild \n 공백 출력
<ul>\n<li></li>....</ul> \n 부터 출력된 것 - firstElementChild li.fr.apple 출력
- Element 가 있는 프로퍼티를 사용해야 의도대로 요소를 가져올 수 있음

hasChildNodes()
- div.box 에 접근해서 hasChildeNodes() 적용해보면
- 자식 요소가 없으니 false를 예상하기 쉬우나 공백도 자식으로 판정해서 true

자식요소 존재 판별 구현
function hasChildren($tag) {
// return $tag.children.length > 0;
return !!$tag.children.length; // 0을 논리로 바꾸면 false
}
parentElement, parentNode
- parentElement, parentNode 둘다 부모 요소 잘 찾음
ex) ul#fruits 의 부모 요소 div.wrap
- parentElement: div.wrap
- parentNode: div.wrap
DOM 제어와 디스트럭쳐링
const [$apple, $banana, $grape]
= [...$wrap.firstElementChild.children];
// $wrap 은 div, div의 첫번째 자식요소는 ul, ul의 children 배열을 분해
형제노드
- previousElementSibling : 이전 형제 요소
- nextElementSibling : 다음 형제 요소


console.log($banana.nextElementSibling === $grape);
console.log($apple.nextElementSibling.nextElementSibling === $grape); // 다음의 다음
// 출력
true
true
기준 태그 탐색
| 기준 태그 위로 탐색 | 기준 태그 아래로 탐색 |
| closest('CSS선택자') | querySelector('CSS선택자') |
const $modifyInput = $label.querySelector('.modify-input');
해당 라벨에서 '.modify-input' 클래스를 가진 첫번째 요소 탐색
<div id="wrapper">
<section class="contentx">
<ul class="list">
<li class="items"><a href="#" class="link">10</a></li>
<li class="items"><a href="#" class="link active">20</a></li>
<li class="items"><a href="#" class="link">30</a></li>
</ul>
<ul class="list">
<li class="items"><a href="#" class="link">40</a></li>
<li class="items"><a href="#" class="link">50</a></li>
<li class="items"><a href="#" class="link">60</a></li>
</ul>
</section>
</div>
const $active = document.querySelector('.active');
console.log($active // a =20
.parentElement // li
.parentElement // ul
.nextElementSibling // ul2
.lastElementChild // li
.firstElementChild // a = 60
);
// 기준 태그로부터 위로 올라가면서 탐색하는 함수
// closest('css selector')
const $contentSect = $active.closest('section.contents');
// 기준 태그 아래방향 탐색
const $found
= $contentSect
.querySelector('ul.list:nth-child(2)')
.querySelector('li.items:last-child')
;
console.log($found);
속성 노드
<input id="name" type="text" value="Hi">
- 문서가 파싱될 때 HTML 요소의 속성은 어트리뷰트 노드로 변환되어 요소 노드의 형제 노드로 추가됨
- 모든 어트리뷰트 노드의 참조는 유사 배열 객체인 NamedNodeMap 객체에 담겨서
요소 노드의 attributes 프로퍼티에 저장됨 - 위 input의 속성(attribute)는 id, type, value 3개의 어트리뷰트 노드 생성
** arraylike 이므로 속성 접근법은 1. 인덱스, 2. 키
<input type="email" id="account" value="abc@def.com">
<script>
const $input = document.querySelector('input');
console.log($input.attributes); // NamedNodeMap {0: type, 1: id, 2: value, type: type, id: id, value: value, length: 3}
console.log($input.attributes[0]);
console.log($input.attributes.type); // type="email"
console.log($input.attributes.type.value); // email
// $input.attributes.type.value = 'range';
$input.attributes.id.value = 'acc';
$input.attributes.value.value = 'abc@naver.com';
</script>
속성 제어
| 속성 추가, 변경 | 속성값 참조 | 속성 삭제 |
| setAttribute(name, value) | getAttribute(name) | removeAttribute(name) |
| $input.setAttribute('type','text') | $input.getAttribute('value') | $input.removeAttribute('style') |
스타일 제어
$div.style.fontSize = '16px';
- style 프로퍼티는 요소 노드의 인라인 스타일을 취득하거나 추가 또는 변경
- style 프로퍼티를 참조하면 CSSStyleDeclaration 타입의 객체를 반환
- 해당 객체는 css 프로퍼티에 대응하는 프로퍼티를 가짐
- css속성을 사용할 때는 카멜케이스를 적용 ( ex. background-color → backgroundColor )

클래스 제어
- className 프로퍼티는 HTML 요소의 class 속성 값을 취득하거나 변경
- classList 프로퍼티는 class 속성의 정보를 담은 DOMTokenList 객체를 반환
- 일반적으로 className 보다는 좀 더 유용한 메서드를 지원하는 classList로 클래스 제어 권장

** className 으로 조작 : 기존 클래스 삭제되고 새로 입력하는 클래스 할당
** classList 로 조작 : add를 통해 추가 가능
// 클래스 조작
$box.className = 'blue'; // box 클래스도 삭제되고 blue클래스만 적용
$box.className = 'box blue';
// 클래스 추가하기
$box.classList.add('circle'); // box green circle
$box.classList.add('aaa', 'bbb', 'ccc'); // box green circle aaa bbb ccc
classList 메서드
| 메서드명 | 설명 |
| add(...className) | 인수로 전달한 클래스를 추가 |
| remove(...className) | 인수로 전달한 클래스를 제거 |
| item (index) | index번째 해당하는 클래스 반환 |
| contains(...className) | 인수 클래스명과 일치하는 클래스 있는지 확인 (T/F 반환) |
| replace(old, new) | 첫번째 인수 클래스를 두번째 인수 클래스로 교체 |
| toggle(className) | 인수로 전달한 클래스명이 속성으로 이미 존재하면 삭제 존재하지 않으면 추가 |
$btn.addEventListener('click', () => {
const CLASS_NAME = 'circle';
const boxClassList = $box.classList;
// 현재 박스가 네모모양이면 원으로 변경
// 원이면 네모로 변경
boxClassList.toggle(CLASS_NAME);
});
// label에 클래스 checked가 없으면 추가, 있으면 삭제
e.target.closest('.checkbox').classList.toggle('checked');
data 어트리뷰트와 dataset 프로퍼티
- data-aaa : aaa에 적절한 단어를 넣은 커스텀 속성을 제어할 수 있음

const [$user1, $user2] = [...document.querySelectorAll('.users li')];
const pororoNumber = $user1.dataset.userNumber;
console.log(typeof pororoNumber); // string 숫자로 쓰려면 형변환
const loopyRole = $user2.dataset.role;
console.log(loopyRole); // admin
// 수정 (객체 문법)
$user2.dataset.role = 'gold';
// 추가
$user1.dataset.userPhoneNumber = '01012344566';
// 삭제
delete $user1.dataset.userNumber;
이벤트
- 브라우저는 클릭, 마우스 이동, 키보드 입력 등이 일어나면 이를 감지하여 특정한 타입의 이벤트 발생
- 애플리케이션이 특정 이벤트에 반응하고 싶다면 이벤트에 대응하는 함수를 브라우저에게 알려주어 호출 위임 가능
- 이 때 호출될 함수를 이벤트 핸들러라고 부르며
이를 위임하는 것을 이벤트 핸들러 등록(binding)이라고 부름 - 이벤트 드리븐 프로그래밍(event driven programming) 이벤트와 그에 대응하는 함수를 통해 프로그램의 흐름을 이벤트 중심으로 제어하는 방식을
이벤트 타입
자주 쓰이는 이벤트 타입
| 타입 | 설명 | |
| 마우스 |
click | 요소 위에서 마우스 왼쪽 버튼을 눌렀을 때 (터치스크린이 있는 장치에선 탭 했을 때) 발생 |
| dblclick | 요소 위에서 마우스 왼쪽 버튼을 두번 빠르게 눌렀을 때 발생 | |
| contextmenu | 요소 위에서 마우스 오른쪽 버튼을 눌렀을 때 발생 | |
| mouseover mouseup |
마우스 커서를 요소 위로 움직였을 때, 커서가 요소 밖으로 움직였을 때 발생 | |
| mouseleave mouseout |
요소 위에서 마우스 왼쪽 버튼을 누르고 있을 때, 마우스 버튼을 뗄 때 발생 | |
| mousemove | 마우스를 움직일 때 발생 | |
| 키보드 | keydown, keyup | 사용자가 키보드 버튼을 누르거나 뗄 때 발생 |
| 폼 |
submit | 사용자가 <form>을 제출할 때 발생 |
| change | 사용자가 <input>과 같은 요소에 값이 변할 때 발생 | |
| focus | 사용자가 <input>과 같은 요소에 포커스 할 때 발생 | |
| blur | 사용자가 <input>과 같은 요소에 포커스를 해제할 때 발생 | |
| resize | 브라우저 창의 크기를 변경할 때 발생 | |
| scroll | 웹페이지 또는 HTML 요소를 스크롤할 때 연속적으로 발생 |
이벤트 핸들러
- 특정 타입의 이벤트가 발생했을 때 브라우저가 실행하는 함수
- 사용자가 만든 이벤트 처리 함수를 브라우저에게 위임하는 개념
- 이런 행위를 이벤트 핸들러 바인딩
** 이벤트 핸들러 바인딩 3가지 방법
1. 어트리뷰트 방식 | 2. 프로퍼티 방식 | 3.콜백함수 방식
1. 어트리뷰트 방식
<div class="box" onclick="boxClickHandler()"></div>
- 이벤트 핸들러를 HTML 요소에 직접 지정하는 방법
- on + 이벤트 타입으로 이루어진 속성명을 적고 속성값으로 이벤트 핸들러 함수의 호출문
- 서로 다른 이벤트라면 하나의 요소에 여러 이벤트 지정 가능
// 서로 다른 이벤트라면 여러 개 지정 가능
<div class="box"
onmouseleave="boxClickHandler()"
onmouseover="makeTextHandler()"></div> // 오렌지 박스에 마우스 올리면 초록박스에 텍스트
2. 프로퍼티 방식
$b1.onclick = sayHelloHandler; 함수 자체를 전달
$element.onclick = function () { 이벤트 실행문 };
- 이벤트 핸들러를 요소 노드의 프로퍼티로 추가하는 방식
- HTML과 자바스크립트를 분리하여 코딩할 수 있어 장점
- 하나의 요소에 하나의 이벤트 핸들러만 바인딩할 수 있어 단점
//이벤트 핸들러
function sayHelloHandler() {
alert('hello!😁');
}
const $b1 = document.getElementById('b1');
$b1.onclick = sayHelloHandler; // 함수 전달 cf. sayHelloHandler() 반환값을 전달
const $b2 = document.getElementById('b2');
$b2.onmouseover = () => {
$b2.style.width = '150px';
};
// 바인딩 할 수 없음
$b3.onmouseleave = sayHelloHandler; // 이전 바인딩은 삭제되고 새로운 바인딩만 연결됨
// 이벤트 핸들러 제거
$b3.onmouseleave = null;
3. 콜백함수 방식
$btn.addEventListener('click', hellohandler);
- addEventListener 메서드는 첫번째 인수로 이벤트 종류를 나타내는 문자열의 이벤트 타입을 전달
- 이벤트타입은 접두사 on을 붙이지 않음
- 두 번째 인수로는 이벤트 핸들러 함수를 콜백으로 전달
- 동일한 요소에서 동일한 이벤트에 대해 하나 이상의 이벤트 핸들러를 등록 가능
- 기존 핸들러 삭제 시 수동으로 해야하고 핸들러는 기명함수여야 함
// 기존 핸들러를 지우고 싶으면 수동으로 지워야 함
// 수동으로 지울 때는 반드시 핸들러가 기명함수이어야 함
$btn.removeEventListener('click', hellohandler);