html body 태그 안에
<script src="index.js"></script>
이 코드로 js 파일 연결
document.getElementById('id');
존재하지 않는 아이디를 입력하면 null값이 나옴
여러 요소들을 한꺼번에 선택할 때 활용
const myTags = document.getElementsByClassName('class');
console.log(myTags[0]);
태그의 순서는 깊이와는 상관없이 위에서부터 차례대로
존재하지 않는 클래스값을 입력하면 빈 HTMLCollection [] 출력
HTMLCollection : 배열과 모양은 같지만 완벽한 배열은 아님 (유사배열)
위 4가지 특징 말고도 유사배열은 다양한 형태를 가지고 있다.
document.getElementsByTagName('태그이름')
명확한 의도 없이 많은 요소들을 한꺼번에 다루게 되면 예상치 못한 실수를 할 가능성이 있기 때문에 많이 사용되는 메소드는 아니다.
document.querySelector('#ID')
document.querySelector('.클래스')
querySelector 메소드를 활용할 때 클래스나 태그 이름처럼 여러 개의 요소가 존재하는 선택자를 쓰더라도 그 중 가장 첫번째 요소 하나만 선택된다.
그래서 여러개의 요소를 선택할 때는 querySelectorAll을 사용한다.
document.querySelectorAll('.클래스')
->css 선택자에 해당하는 태그 모음을 NodeList()로 출력, 요소가 하나밖에 없더라도 요소가 하나인 NodeList를 반환한다.
이벤트 핸들러를 등록하는 2가지 방법
1. 자바스크립트로 해당 DOM 객체의 onclick 프로퍼티에 등록하기
const myBtn = document.querySelector('#grade');
myBtn.onclick = function(){
alert('메세지');
}
<button id="myBtn" onclick="console.log('Hello Codeit!')">클릭!</button>
윈도우 객체 : 브라우저 창을 대변하면서 자바스크립트에서 최상단에 존재하는 객체
자바스크립트 코드 어느 곳에서나 항상 접근할 수 있기 때문에 전역객체, Global Object라고 불림
문서 객체 모델
웹 페이지에 나타나는 HTML 문서 전체를 객체로 표현한 것.
웹 페이지의 태그들을 객체처럼 다룰 수 있다.
1. 출력하는 자료형이 다르다.
dir 메소드는 문자열 표시 형식으로 콘솔에 출력합니다.
2. log는 값 자체에, dir은 객체의 속성에!
log 메소드는 파라미터로 전달받은 값을 위주로 출력하는 반면, dir 메소드는 객체의 속성을 좀 더 자세하게 출력한다.
dir 메소드에서는 객체의 유형이 먼저 출력되고, 함수 부분에서는 클릭해서 펼쳤을 때 함수가 가진 속성들을 더 보여준다.
3. log는 여러 개, dir은 하나만!
log 메소드는 여러 값을 쉼표로 구분해서 전달하면 전달받은 모든 값을 출력하는 반면, dir 메소드는 여러 값을 전달하더라도 첫 번째 값만 출력한다.
4. DOM 객체를 다룰 때..
log 메소드는 대상을 HTML 형태로 출력하고, 객체의 속성에 좀 더 중점을 둔 dir 메소드는 대상을 객체 형태로 출력합니다.
각 객체를 노드라는 용어로 표현,
부모 노드 - 자식 노드
형제 노드
요소 노드 : 태그노드
텍스트 노드 : 요소 노드의 자식 노드, 자식 노드를 가질 수 없다.
const myTag = document.querySelector('#list-1');
console.log(myTag);
// 형제 요소 노드
console.log(myTag.previousElementSibling);
console.log(myTag.nextElementSibling);
// 부모 요소 노드
console.log(myTag.parentElement);
// 자식 요소 노드
console.log(myTag.children[1]);
console.log(myTag.firstElementChild);
console.log(myTag.lastElementChild);
element.children
: 자식 요소 노드, element의 자식 요소 모음(HTMLCollection)
element.firstElementChild
: 자식 요소 노드, element의 첫 번째 자식 요소 하나
element.lastElementChild
: 자식 요소 노드, element의 마지막 자식 요소 하나
element.parentElement
: 부모 요소 노드, element의 부모 요소 하나
element.previousElementSibling
: 형제 요소 노드, element의 이전(previous) 혹은 좌측(left)에 있는 요소 하나
element.nextElementSibling
: 형제 요소 노드, element의 다음(next) 혹은 우측(right)에 있는 요소 하나
요소 노드가 아닌, 다른 노드들을 이동하고 싶은 경우
node.childNodes
: 자식 노드, node의 자식 노드 모음(NodeList)
node.firstChild
: 자식 노드, node의 첫 번째 자식 노드 하나
node.lastChild
: 자식 노드, node의 마지막 자식 노드 하나
node.parentNode
: 부모 노드, node의 부모 요소 하나
node.previousSibling
: 형제 노드, node의 이전(previous) 혹은 좌측(left)에 있는 노드 하나
node.nextSibling
: 형제 노드, node의 다음(next) 혹은 우측(right)에 있는 노드 하나
브라우저가 HTML 코드를 해석할 때 각 코드들은 상황에 맞게 node를 생성하고 DOM 트리를 구성하는데,
HTML 태그는 요소 노드가 되고,
문자들은 텍스트 노드,
그리고 주석도 주석 노드로 DOM 트리에 반영된다.
요소 노드 내부의 HTML 코드를 문자열로 리턴해준다. (내부에 있는 줄 바꿈이나 들여쓰기 모두 포함)
요소 안의 정보를 확인할 수도 있지만, 내부의 HTML 자체를 수정할 때 좀 더 자주 활용된다. (내부에 있던 값을 완전히 새로운 값으로 교체하기 때문에 주의해서 사용해야 함)
요소 노드 자체의 전체적인 HTML 코드를 문자열로 리턴해준다. (내부에 있는 줄 바꿈이나 들여쓰기 모두 포함)
outerHTML은 새로운 값을 할당할 경우 요소 자체가 교체되어 버리기 때문에 주의해야 한다.
요소 안의 내용들 중에서 HTML 태그 부분은 제외하고 텍스트만 가져온다. (내부에 있는 줄 바꿈이나 들여쓰기 모두 포함)
새로운 값을 할당하면 innerHTML과 마찬가지로 내부의 값을 완전히 새로운 값으로 교체 한다.(수정)
textContent는 말그대로 텍스트만 다루기 때문에, 특수문자도 그냥 텍스트로 처리한다.
const myTag = document.querySelector('#list-1');
console.log(myTag.textContent);
myTag.textContent = '<li>new text!</li>';
=><li>new text!</li>
로 출력
document.createElement('태그 이름');
element.textContent, element.innerHTML, ...
element.prepend(); //메소드를 호출한 노드의 제일 첫번째 노드로 파라미터로 전달한 값을 추가할 수 있음
element.append(); //메소드를 호출한 노드의 제일 마지막 자식 노드로 파라미터로 전달한 값을 추가할 수 있음
element.after(); //메소드를 호출한 노드의 앞쪽으로 파라미터로 전달한 값을 추가할 수 있음, 형제 노드
element.before(); //메소드를 호출한 노드의 뒤쪽으로 파라미터로 전달한 값을 추가할 수 있음, 형제 노드
여러개의 값을 전달하면 전달한 순서대로 노드 추가 가능
prepend나 before 여러 개의 파라미터 추가하면 역순이 아니라 순서대로 한번에 추가된다.
today.append(tomorrow.children[1]);
tomorrow.children[1].after(today.children[1]);
tomorrow.children[2].before(today.children[1]);
tomorrow.lastElementChild.before(today.children[1]);
요소 노드 삭제하기
노드.remove();
노드.children[2].remove();
element.getAttribute('속성');
표준, 비표준 속성에 다 접근 가능
element.setAttribute('속성','값');
element.removeAttribute('속성','값');
속성이름에서는 대소문자를 구분하지 않는다.
element.style.styleName = 'value';
element.className, element.classList
today.children[0].style.textDecoration = 'line-through';
done : css 클래스 스타일
classList.add
,classList.remove
: 여러 개의 값을 전달하면 여러 클래스 삭제 가능
classList.toggle
: 있으면 제거하고 없으면 추가하는 메소드, 하나의 값만 적용 가능하고 두번째 파라미터로 추가또는 삭제 기능을 강제할 수 있음
// elem.classList: add, remove, toggle
const item = tomorrow.children[1];
item.classList.add('done');
item.classList.remove('done');
item.classList.toggle('done');
// elem.className
today.children[1].className = 'done';
가장 간단하게는 아래와 같이 querySelector로 태그를 선택할 때 css 선택자를 활용해서 태그를 선택하는 데에 활용할 수 있다.
const fields = document.querySelectorAll('[field]');
console.log(fields);
비표준 속성은 객체 형태의 데이터가 있을 때, 각 프로퍼티 값들이 들어갈 태그를 구분하는데 활용할 수 있다.
const fields = document.querySelectorAll('[field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.getAttribute('field');
tag.textContent = task[field];
}
getAttribute 메소드를 활용해서 속성값을 가져오고, setAttribute 메소드를 활용해서 속성값을 설정해주는 원리로 이벤트를 통해 실시간으로 스타일을 변경하거나 데이터를 변경하는데 활용할 수 있다.
때로는 class를 다루는 것보다 setAttribute로 비표준 속성을 변경하는게 스타일을 다루기에 오히려 편리한 경우도 있다.
const fields = document.querySelectorAll('[field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.getAttribute('field');
tag.textContent = task[field];
}
const btns = document.querySelectorAll('.btn');
for (let btn of btns) {
const status = btn.getAttribute('status');
btn.onclick = function () {
fields[2].textContent = status;
fields[2].setAttribute('status', status);
};
}
비표준 속성을 사용해 코드를 작성하면 시간이 지나서 나중에 그 속성이 표준으로 등록될때 문제가 발생할 수 있다.
그래서 비표준 속성을 사용하기 위해 data-* 속성이라는 방식이 존재한다.
data-로 시작하는 속성은 모두 dataset이라는 프로퍼티에 저장되는데 예를 들어서 data-status라는 속성이 있다면, element.dataset.status라는 프로퍼티에 접근해서 그 값을 가져올 수 있다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>JS with Codeit</title>
</head>
<body>
<p>할 일 : <b data-field="title"></b></p>
<p>담당자 : <b data-field="manager"></b></p>
<p>상태 : <b data-field="status"></b></p>
<div>
상태 변경:
<button class="btn" data-status="대기중">대기중</button>
<button class="btn" data-status="진행중">진행중</button>
<button class="btn" data-status="완료">완료</button>
</div>
<script src="index.js"></script>
</body>
</html>
[data-status] {
padding: 5px 10px;
}
[data-status="대기중"] {
background-color: #FF6767;
color: #FFFFFF;
}
[data-status="진행중"] {
background-color: #5f62ff;
color: #FFFFFF;
}
[data-status="완료"] {
background-color: #07c456;
color: #FFFFFF;
}
const fields = document.querySelectorAll('[data-field]');
const task = {
title: '코드 에디터 개발',
manager: 'CastleRing, Raccoon Lee',
status: '',
};
for (let tag of fields) {
const field = tag.dataset.field;
tag.textContent = task[field];
}
const btns = document.querySelectorAll('.btn');
for (let btn of btns) {
const status = btn.dataset.status;
btn.onclick = function () {
fields[2].textContent = status;
fields[2].dataset.status = status;
};
}
이벤트 헨들러를 등록하는 방법
btn.onclick = function() {
console.log('click!');
};
새로운 이벤트 핸들러를 할당하면 기존의 코드를 덮어쓰기 때문에 중요한 이벤트를 덮어쓰는 실수를 할 수 있다.
권장되는 방법
하나의 요소에 여러개의 독립적인 이벤트 핸들러를 등록할 수 있다.
element.addEventListener('event', 'handler');
element.removeEventListener('event', 'handler');
이벤트를 삭제할 때 반드시 등록할때 사용했던 핸들러를 그대로 전달해야 한다.
이벤트를 등록할 때, 외부에 함수를 만들어서 해당 함수의 이름으로 핸들러를 전달해줘야 한다.
이벤트 핸들러 부분에는 함수 이름만 전달해주면 된다. (소괄호 같은거 붙일 필요 없음)
예시 코드
let btn = document.querySelector('#myBtn');
// btn.onclick = function () {
// console.log('Hi Codeit!');
// };
function event1() {
console.log('Hi Codeit!');
}
function event2() {
console.log('Hi again!');
}
// elem.addEventListener(event, handler)
btn.addEventListener('click', event1);
btn.addEventListener('click', event2);
// elem.removeEventListener(event, handler)
btn.removeEventListener('click', event2);
mousedown : 마우스 버튼을 누르는 순간
mouseup : 마우스 버튼을 눌렀다 떼는 순간
click : 왼쪽 버튼을 클릭한 순간dblclick 왼쪽 버튼을 빠르게 두 번 클릭한 순간
contextmenu : 오른쪽 버튼을 클릭한 순간
mousemove : 마우스를 움직이는 순간
mouseover : 마우스 포인터가 요소 위로 올라온 순간
mouseout : 마우스 포인터가 요소에서 벗어나는 순간
mouseenter : 마우스 포인터가 요소 위로 올라온 순간 (버블링이 일어나지 않음)
mouseleave : 마우스 포인터가 요소에서 벗어나는 순간 (버블링이 일어나지 않음)
keydown : 키보드의 버튼을 누르는 순간
keypress : 키보드의 버튼을 누르는 순간 ('a', '5' 등 출력이 가능한 키에서만 동작하며, Shift, Esc 등의 키에는 반응하지 않음)
keyup : 키보드의 버튼을 눌렀다 떼는 순간
focusin : 요소에 포커스가 되는 순간
focusout : 요소로부터 포커스가 빠져나가는 순간
focus : 요소에 포커스가 되는 순간 (버블링이 일어나지 않음)
blur : 요소로부터 포커스가 빠져나가는 순간 (버블링이 일어나지 않음)
change : 입력된 값이 바뀌는 순간
input : 값이 입력되는 순간
select : 입력 양식의 하나가 선택되는 순간
submit : 폼을 전송하는 순간
scroll : 스크롤 바가 움직일 때
resize : 윈도우 사이즈를 움직일 때 발생
더 많은 프로퍼티 들이 있지만 자주 사용되는 이벤트 객체의 프로퍼티들을 정리해보자면
이벤트 타입과 상관없이 모든 이벤트 객체들이 공통적으로 가지고 있는 프로퍼티
type : 이벤트 이름 ('click', 'mouseup', 'keydown' 등)
target : 이벤트가 발생한 요소
currentTarget : 이벤트 핸들러가 등록된 요소
timeStamp : 이벤트 발생 시각(페이지가 로드된 이후부터 경과한 밀리초)
bubbles : 버블링 단계인지를 판단하는 값
마우스와 관련된 이벤트
button : 누른 마우스의 버튼 (0: 왼쪽, 1: 가운데(휠), 2: 오른쪽)
clientX, clientY : 마우스 커서의 브라우저 표시 영역에서의 위치
pageX, pageY : 마우스 커서의 문서 영역에서의 위치
offsetX, offsetY : 마우스 커서의 이벤트 발생한 요소에서의 위치
screenX, screenY : 마우스 커서의 모니터 화면 영역에서의 위치
altKey : 이벤트가 발생할 때 alt키를 눌렀는지
ctrlKey : 이벤트가 발생할 때 ctrl키를 눌렀는지
shiftKey : 이벤트가 발생할 때 shift키를 눌렀는지
metaKey : 이벤트가 발생할 때 meta키를 눌렀는지 (window는 window키, mac은 cmd키)
키보드와 관련된 이벤트
key : 누른 키가 가지고 있는 값
code : 누른 키의 물리적인 위치
altKey : 이벤트가 발생할 때 alt키를 눌렀는지
ctrlKey : 이벤트가 발생할 때 ctrl키를 눌렀는지
shiftKey : 이벤트가 발생할 때 shift키를 눌렀는지
metaKey : 이벤트가 발생할 때 meta키를 눌렀는지 (window는 window키, mac은 cmd키)
function updateToDo(event) {
event.target.classList.toggle('done');
}
여기서 target을 안 넣으면 어떤 요소에 toggle을 넣을지 지정을 안 해준 것과 같다.
이벤트가 상위 요소로 전파되는 단계
하나의 요소에 이벤트가 발생하게 되면 이벤트가 동작하고 같은 타입의 이벤트에 한해서 부모 요소의 핸들러도 동작하는 현상.
요소 각각에 할당된 모든 요소들이 동작
이벤트 객체의 타겟 프로퍼티는 변하지 않는다!!
부모 요소의 핸들러들이 최초의 이벤트가 발생한 시작점을 알 수 있다.
e.currentTarget
: 실행중인 할당된 요소에 접근하고 싶은 경우 이 프로퍼티를 활용하면 된다.
e.stopPropagation();
이벤트를 막을 수 있음 하지만 자주 사용하지는 않음 버블링을 막을 일이 그렇게 많지 않기 때문,
이벤트가 하위 요소로 전파되는 단계
캡쳐링은 이벤트가 발생하면 가장 먼저, 그리고 버블링의 반대 방향으로 진행되는 이벤트 전파 방식이다.
이벤트가 발생하면 가장 먼저 window 객체에서부터 target 까지 이벤트 전파가 일어난다. (캡쳐링 단계)
그리고 나서 타깃에 도달하면 타깃에 등록된 이벤트 핸들러가 동작하고, (타깃 단계)
이후 다시 window 객체로 이벤트가 전파된다. (버블링 단계)
캡쳐링 단계에서 이벤트를 발생시키는 경우는 매우 드물다.
타깃 단계에서 등록된 핸들러가 있으면 해당 이벤트 핸들러가 먼저 동작한 이후에 버블링 단계에서 각 부모 요소에 등록된 이벤트 핸들러가 있으면 그 때 해당 이벤트 핸들러가 동작하는 것이 일반적이다.
캡쳐링 단계에서 이벤트 핸들러를 동작시키려면, addEventListener에 세번째 프로퍼티에 true 또는 { capture:true }를 전달하면 된다.
버블링을 허용한 이벤트 위임
새로 자식 요소를 추가하거나 삭제하더라도 이벤트 제어를 잘 할 수 있다.
여러개의 핸들러를 만들지 않아도 되어 프로그램의 성능에 도웅이된다.
자식 요소에서 버블링을 막을 경우 동작하지 않는다.
원하는 자식요소에서만 동작이 작동하도록 하기 위해 아래와 같이 지정된 태그네임의 경우에만 동작을 할 수 있도록 할 수 있다.
if(e.target.tagName === 'LI')
if (e.target.classList.contains('item')) {
e.target.classList.toggle('done');
}
event.preventDefault();
동작을 막을 수 있다.
파라미터로 아무 값도 전달하지 않고 그냥 소괄호를 여닫는 것으로 메소드를 호출하면 된다.
0: 마우스 왼쪽 버튼
1: 마우스 휠
2: 마우스 오른쪽 버튼
click: 마우스 왼쪽 버튼을 눌렀을 때
contextmenu: 마우스 오른쪽 버튼을 눌렀을 때
dblclick: 동일한 위치에서 빠르게 두번 click할 때
mousedown: 마우스 버튼을 누른 순간
mouseup: 마우스 버튼을 눌렀다 뗀 순간
mousemove: 마우스 포인터가 움직일 때
mouseover: 마우스 포인터가 요소 밖에서 안으로 움직일 때
mouseout: 마우스 포인터가 요소 안에서 밖으로 움직일 때
MouseEvent.target
: 이벤트가 발생한 요소
MouseEvent.relatedTarget
: 이벤트가 발생하기 직전(또는 직후)에 마우스가 위치해 있던 요소
mouseenter
: 마우스 포인터가 요소 바깥에서 안쪽으로 들어갈 때,
mouseleave
: 마우스 포인터가 요소 안쪽에서 바깥으로 나갈 때 발생
이벤트가 자식 요소에 영향끼치는지가 둘의 가장 큰 차이
이벤트 핸들러가 자식 요소에까지 영향을 끼치게 하고싶은 경우에는 mouseover/mouseout을, 자식 요소에는 영향을 끼치지 않고 해당 요소에만 이벤트 핸들러를 다루고자 한다면 mouseenter/mouseleave를 활용하면 좋다.