- Window, DOM, BOM 이란?
- DOM 가져오기
- DOM 추가 / 수정 / 삭제
- DOM 이벤트 처리
브라우저의 가장 최상위 전역 객체로 DOM와 BOM로 구성되어 있다.
대부분의 브라우저에서 사용되고 선언된 객체는 window
객체의 속성으로 저장된다.
전역 객체를 참조하는 this
는 window
와 같다.
var로 선언된 변수는 window 객체 속성으로 추가되고, let이나 const로 선언된 변수는 추가되지 않는다.
// var로 선언한 변수는 window 객체에 속성으로 추가됨
var myVar = 10;
console.log(window.myVar); // 10
console.log(this.myVar); // 10
console.log(this === window); // true
// let이나 const로 선언한 변수는 window 객체에 추가되지 않음
let myLetVar = 20;
const myConstVar = 30;
console.log(window.myLetVar); // undefined
console.log(window.myConstVar); // undefined
브라우저가 웹 페이지를 로드할 때,
브라우저 엔진이 메모리(RAM)에 window 객체를 생성하고,
window 객체에는 DOM과 BOM 등의 정보가 담긴다.
이후 JavaScript 엔진이 window 객체를 통해 웹 페이지의 요소들과 상호작용한다.
브라우저가 HTML 문서을 이해할 수 있도록 객체화한 구조
태그마다 일대일로 객체를 만든다.
문서노드(document node) - 트리의 최상위 노드, 시작점
요소노드(element node) - 태그
어트리뷰트노드(attribute node) - 태그 안 속성들
텍스트노드(text node) - 태그 내 텍스트, 요소노드의 자식이며 DOM 트리의 최종단
브라우저와 소통하는 데 필요한 객체들의 집합
document 문서가 아닌 window를 제어
navigator, location, document, screen, history로 구성
DOM, BOM 을 제어
DOM을 제어할 때 window.document.querySelector
또는 document.querySelector
BOM을 제어할 때 window.location
querySelector은 DOM 안에 메서드이고, location은 window 안에 객체이다.
document.querySelectorAll('ul > li:last-child')
css 선택자에 대응하는 모든 요소들 반환
document.querySelectorAll(':hover')
가상 클래스(pseudo-class)도 사용
querySelector
css 선택자에 대응하는 첫 번째 요소 반환
elem.querySelectorAll(css)[0]
와 동일
getElementById
해당 ID를 가진 하나의 요소 반환
getElementByName
getElementsByTagName
getElementsByClassName
해당하는 요소를 담은 컬렉션 반환
살아있는 컬렉션을 반환하고, 문서에 변경이 생기면 자동 갱신된다.
<div>첫 번째 div</div>
<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>
<div>두 번째 div</div>
<script>
alert(divs.length); // 2
</script>
querySelectorAll은 정적인 컬렉션 반환하고, 문서에 변경되어도 반영하지 못한다
<div>첫 번째 div</div>
<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>
<div>두 번째 div</div>
<script>
alert(divs.length); // 1
</script>
elem.closest(css selector)
DOM 요소에서 css 선택자에 해당하는 가장 가까운 상위 요소를 반환
<div class="parent">
<div class="child">
<div class="grandchild"></div>
</div>
</div>
const grandchild = document.querySelector('.grandchild');
// closest() 메소드 사용
const parent = grandchild.closest('.parent');
console.log(parent); // <div class="parent">...</div>
str.matches(정규식)
정규식과 일치하는 문자열을 배열로 반환
var re = /see (chapter \d+(\.\d)*)/i;
var str = "For more information, see Chapter 3.4.5.1";
var result = str.match(re);
console.log(result);
// 결과값
[
"see Chapter 3.4.5.1", // 전체 일치한 문자열
"Chapter 3.4.5.1", // 첫 번째 그룹 "(chapter \d+(\.\d)*)"
".1" // 두 번째 그룹 "(\.\d)"
]
var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var regexp = /[A-E]/gi; //g 플래그(모든 일치 항목을 찾음), i 플래그(대소문자를 구분하지 않음)
var matches_array = str.match(regexp);
console.log(matches_array);
// ['A', 'B', 'C', 'D', 'E', 'a', 'b', 'c', 'd', 'e']
eleA.contains(elemB)
elemB가 eleA의 자손인지 여부에 따라 boolean 값 반환
createElement
해당 태그를 가진 HTML 요소를 만들어 반환
appendChild
하나의 노드를 부모의 마지막 자식으로 추가, 추가된 노드를 반환
append
다수의 노드나 텍스트를 부모의 마지막 자식으로 추가, 반환값 없음
var newDiv = document.createElement('div'); // <div></div> 생성
var newText1 = document.createTextNode("Hello"); // 텍스트 노드 1 생성
var newText2 = document.createTextNode(" World!"); // 텍스트 노드 2 생성
newDiv.appendChild(newText1); // <div>Hello</div>
newDiv.appendChild(newText2); // <div>Hello World!</div>
document.body.appendChild(newDiv); // <body> 안에 <div>Hello World!</div> 추가
var newDiv = document.createElement('div'); // <div></div> 생성
// append()는 여러 텍스트와 노드를 동시에 추가 가능
newDiv.append("Hello", " World!", document.createElement("span"));
// <div>Hello World!<span></span></div>
document.body.append(newDiv); // <body> 안에 <div>Hello World!<span></span></div> 추가
innerHTML
HTML 태그를 포함한 전체 내용을 문자열로 반환
innerText
화면에 보이는 텍스트만 가져오며, HTML 태그, 스타일 무시
textContent
모든 텍스트를 가져오며, HTML 태그, 스타일 무시
<div id='content'>
text1
<span style='color: red'>text2</span>
<span style='display:none'>text3</span>
</div>
<script>
const content = document.getElementById('content');
console.log(content.innerHTML);
// 결과:
// text1
// <span style='color: red'>text2</span>
// <span style='display:none'>text3</span>
console.log(content.innerText);
// 결과:
// text1
// text2
console.log(content.textContent);
// 결과:
// text1
// text2
// text3
</script>
innerHTML는 HTML 태그를 해석해서 DOM에 반영하고, HTML을 텍스트로 출력하려면 태그 기호가 HTML 엔티티로 변환하여 출력한다.
innerText와 textContent는 HTML 태그를 해석하지 않고 텍스트로 처리하며, innerText는 화면에 보이는 텍스트만, textContent는 숨겨진 텍스트까지 모두 가져온다.
<div id='example'></div>
<script>
const example = document.getElementById('example');
// 1. innerHTML을 사용하여 값을 넣기
example.innerHTML = "<strong style='color: green'>text</strong>";
console.log(example.innerHTML); // <strong style='color: green'>text</strong>
console.log(example.innerText); // text
console.log(example.textContent); // text
//-----------------------------------------------------
// 2. innerText를 사용하여 값을 넣기
example.innerText = "<em style='color: blue'>text</em>";
console.log(example.innerHTML); // <em style='color: blue'>text</em>
console.log(example.innerText); // <em style='color: blue'>text</em>
console.log(example.textContent); // <em style='color: blue'>text</em>
//-----------------------------------------------------
// 3. textContent를 사용하여 값을 넣기
example.textContent = "<span style='color: red'>text</span>";
console.log(example.innerHTML); // <span style='color: red'>text</span>
console.log(example.innerText); // <span style='color: red'>text</span>
console.log(example.textContent); // <span style='color: red'>text</span>
</script>
getAttribute
요소가 가지고 있는 속성의 값을 반환, 없으면 null 반환
setAttribute
요소에 새로운 속성과 값을 설정, 반환값 없음(undefined)
removeAttribute
요소에서 특정 속성 제거, 반환값 없음(undefined)
<div></div>
<script>
const elem = document.querySelector('div');
elem.setAttribute('id', 'wrap');
console.log(elem.outerHTML); // <div id="wrap"></div>
console.log(elem.getAttribute('id')); // 반환값: 'wrap'
elem.removeAttribute('id');
console.log(elem.outerHTML); // <div></div>
</script>
elem.innerHTML=''
요소의 HTML 내용을 빈 문자열로 교체하여 모든 자식 요소를 삭제한다.
elem의 자식요소가 모두 삭제되고 빈 문자열을 HTML로 파싱하여 DOM 트리가 다시 만들어지고, elem의 자식 요소로 넣는다.
재파싱이 필요하여 성능이 저하될 수 있다.
elem.textContent=''
elem 내 모든 텍스트 노드를 삭제한다.
DOM을 재파싱하지 않는다.
elem.replaceChildren()
elem의 모든 자식 요소들이 삭제된다.
DOM 요소를 직접 다루기 때문에 재파싱이 필요 없다.
최적화된 방식으로 DOM을 처리하므로 성능이 매우 좋다.
removeChild()
elem에서 첫 번째 자식 노드를 하나씩 가져와 삭제한다.
while(elem.firstChild) { elem.removeChild(elem.firstChild); }
remove()
while(elem.firstChild) { elem.firstChild.remove(); }
이벤트란 웹 페이지에서 발생하는 사용자 상호 작용이다.
마우스 이벤트
click
마우스 클릭
dblclick
마우스 더블클릭
mousedown
마우스 누를 때
mouseup
마우스 뗐을 때
mousemove
마우스 움직일 때
mouseover
마우스가 요소 위로 올라갈 때
mouseout
마우스가 요소 바깥으로 나갈 때
키보드 이벤트
keydown
키보드 누를 때
keyup
키보드 뗐을 때
keypress
키보드를 누르고 뗐을 때 모두
폼 이벤트
submit
폼을 제출할 때
change
입력 요소 값이 변경될 때
input
입력 요소에 사용자가 입력할 때
포커스 이벤트
focus
요소에 포커스될 때
blur
요소에 포커스가 해제될 때
윈도우 이벤트
load
자원이 로드될 때
resize
창 크기가 변경될 때
scroll
스크롤바가 움직일 때
e.target.value
이벤트가 실제로 발생한 요소의 현재 값 반환
e.currentTarget
이벤트 핸들러가 연결된 요소
<div id="parent">
<button id="child">Click me</button>
</div>
<script>
const parent = document.getElementById("parent");
parent.addEventListener("click", (e) => {
console.log("target:", e.target); // 클릭된 실제 요소 (button)
console.log("currentTarget:", e.currentTarget); // 이벤트가 걸린 요소 (div)
});
</script>
e.preventDefault()
이벤트의 기본 동작(새로고침, 링크 이동 등)을 막음, ex) 폼 제출 시 페이지 새로고침 막음
e.stopPropagation()
이벤트 전파(버블링, 캡처링) 차단
addEventListener
메서드를 통해 요소에 이벤트 핸들러(실행될 함수)를 등록하는 방식
<button id="button">클릭</button>
var Button = document.getElementById('button');
Button.addEventListener('click', myEvent);
function myEvent() {
alert('클릭!');
}
click 이벤트 발생 시 alert('클릭!');가 실행된다.
만약 Button.addEventListener('click', myEvent());
로 함수에 괄호를 붙이면 어떻게 될까?
함수를 즉시 실행하라는 의미로, 클릭 이벤트가 발생하지 않아도 함수가 바로 호출된다.
즉, myEvent() 함수의 반환값을 이벤트 핸들러로 전달하게 되어 반환하지 않는다면 undefined가 전달되어 이벤트 발생 시 아무 일도 일어나지 않게 된다.
또 어떻게 함수를 정의하기 전에 이벤트 핸들러로 전달할 수 있었을까?
함수가 호이스팅
되었기 때문인데, JS에서 선언된 변수와 함수는 코드 실행 전에 상단으로 끌어올려진다. 단, let와 const로 선언된 변수는 초기화 전에 접근하면 에러가 난다.
var Button = document.getElementById('button');
Button.addEventListener('click', myEvent); //error
const myEvent = () => {
alert('클릭!');
}
HTML 태그 내에서 인라인 이벤트 핸들러 onclick
의 속성에 함수를 문자열로 작성하여 이벤트를 처리할 수 있다.
<button>클릭</button>
문자열로 작성된 코드를 실행하기 때문에 ()을 붙여야 한다. 생략하면 참조만 하고 호출하지 않는다.
이 문자열을 실행 가능한 JS 코드로 변환하여 처리한다.
근데 인라인 이벤트 핸들러는 가독성, 유지보수 등의 이유로 추천되지 않는다.
HTML과 JS를 다른 파일로 분리하는 것이 바람직하다.
여기서 궁금한게 있다. 리액트에서 인라인 이벤트 핸들러처럼 이벤트를 처리하지 않나?
결론은 인라인 이벤트 핸들러와는 다르다.
const handleClick = () => {
alert('클릭!');
};
<button onClick={handleClick}>클릭</button>
JSX는 JS로 컴파일될 때, 내부적으로 addEventListener
로 처리된다.
window 객체에 이벤트 리스너를 등록한 경우
function handleResize() {
console.alert("Resizing the browser.");
}
window.addEventListener("resize", handleResize);
DOM 요소에 이벤트 리스너를 등록한 경우
const title = document.querySelector("#title");
title.innerHTML = "This is JAVASCRIPT!!!! ";
title.style.color = "white";
function handleClick() {
title.style.color = "blue";
}
title.addEventListener("click", handleClick);
등록된 이벤트 리스너를 removeEventListener
메서드로 제거할 수 있다.
function handleClick() {
console.log("Button clicked!");
}
const button = document.querySelector('button');
button.addEventListener('click', handleClick);
button.removeEventListener('click', handleClick);
다음 상황에서 제거해야 한다.
리액트에서 useEffect에서 마운트될 때 이벤트 리스너를 등록하고 언마운트 또는 의존성 배열이 변경되며 컴포넌트가 사라질 때 return안에서 이벤트 리스너가 남아 있지 않도록 하여 메모리 누수를 방지할 수 있다.
useEffect(() => {
function handleResize() {
console.log('Resizing the window');
}
// 이벤트 리스너 등록
window.addEventListener('resize', handleResize);
// 컴포넌트가 언마운트될 때 이벤트 리스너 제거
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
레퍼런스
https://velog.io/@kim_unknown_/JavaScript-Difftext
https://hianna.tistory.com/722
https://ramincoding.tistory.com/entry/JavaScript-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%95%B8%EB%93%A4%EB%9F%AC-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EA%B0%9D%EC%B2%B4-evente