원래 자바스크립트는 웹 브라우저에서 사용하려고 만든 언어이나 이후 다양한 진화를 거쳐 다양한 플렛폼에서 사용하게 되었습니다. 자바스크립트가 돌아가는 플랫폼을 호스트라고 부릅니다. 호스트는 브라우저, 웹서버, 등등 다양한 플렛폼이 될수 있습니다. 호스트 환경은 랭귀지 코어(ECMAScript)에 더하여 플렛폼에 특정되는 다양한 객체와 함수를 제공합니다.
Node.js는 서버 사이드기능을 제공하며,웹페이지는 웹페이지를 제어하기 위한 수단을 제공합니다.
호스트환경이 웹브라우저일 때의 사용할 수 있는기능을 개괄적으로 보여줍니다.
최상단에 위치한 window 객체는 두가지 역할을 합니다.
function sayHi() {
alert("안녕하세요.");
}
// 전역 함수는 전역 객체(window)의 메서드임
window.sayHi();
alert(window.innerHeight); // 창 내부(inner window) 높이
문서객체모델 DOM(Document Object Model)은 웹페이지내의 모든 콘텐츠를 객체로 나타내줍니다. DOM은 CRUD(Create, Read, Update,Delete)가 가능합니다. document 객체는 페이지의 기본 진입점 역할을 합니다. document 객체를 이용하여 페이지내의 모든것을 변경하거나 만들어낼 수 있습니다.
💡 DOM은 웹브라우저 호스트에서만 사용되지 않습니다. html을 로드해서 가공해주는 서버사이드 웹서버에서도 DOM이 사용됩니다. 이런 스크립트 에서는 명세서 일부만을 지원합니다.
💡 CSSOM (CSS Object Model) : CSS 규칙과 스타일시트는 HTML과 다른구조를 띕니다. 따라서 CSS의 규칙과 스타일 시트를 객체로 나타내고 이를 어떻게 읽고 쓸수 있을지 설명을 담은 별도의 명세서 CSSOM이 존재합니다. 이객체는 문서에 쓰이는 스타일을 수정할 때 DOM과 함께 쓰입니다.
BOM (Browser Object Model) : document를 제외한 모든 것을 제어하기위해서 웹 호스트가 제공하는 기능들입니다. 이러한 기능들에는
- navigator : 브라우저와 운영체제에 대한 정보들을 알려줍니다.
- location : 현재 url을 읽을 수 있게해주고 새로운 url로 redirect 할수 있도록 해줍니다.
- alert, confirm, prompt :
DOM의 생성과정에서는 짝이 안맞는 태그등을 잡아서 생성시켜주고 최대한 HTML을 정상적으로 교정하여 HTML문서를 생성하고 DOM트리를 생성합니다.
HTML의 모든것은 DOM에 추가됩니다. 그것이 주석이든 무엇이든 간에 DOM에 추가됩니다. 노드 타입은 총 12가지 이나 실무에서는 주로 4가지를 다룹니다.
<body>
document.body가 존재하지 않을 수도있습니다.<html>
<head>
<script>
alert( "HEAD: " + document.body ); // null, 아직 <body>에 해당하는 노드가 생성되지 않았음
</script>
</head>
<body>
<script>
alert( "BODY: " + document.body ); // HTMLBodyElement, 지금은 노드가 존재하므로 읽을 수 있음
</script>
</body>
</html>
<head>
에 해당하는 노드입니다. 자식 노드(chilren, child node)는 바로 아래의 자식노드를 나타냅니다. 자식 노드는 부모 노드의 바로 아래의 노드입니다.
<head>
,<body>
는 <html>
의 바로 아래의 노드입니다.
후손 노드는 자식관계에서 직계로 내려오는 관계에 놓인 노드를 의미합니다.
const section1ChildNode = document.body.querySelector('#section--1').childNodes;
for (const node of section1ChildNode) {
console.log(node);
}
/*
section--1 이라는 id값을 가진 노드의 모든 자식노드를 받는다.
이를 풀어 한 노드씩 출력한다.
*/
childeNodes
는 대상 노드의 모든 자식 노드를 collection
로 반환합니다. 이 collection
은 유사배열 객체로서 iterable 합니다. 하지만 배열은 아닙니다.
❗️ iterable 하기 때문에 for of 구문을 사용하지만 for in 구문을 사용하면 안됩니다. for in은 굳이 순회하지 않아도될 모든 프로퍼티까지 모두 돌기 때문입니다.
collection
에는 거의 쓰이지 않는 프로퍼티가 있어 이것 까지 모두 순회하기 때문입니다.
얼핏 보면 위의 childNodes
와 비슷해 보일수 있지만 이 코드는 HTMLCollection으로 iterable한 객체로 반환한다. 이 객체 안에는 HTML 태그의 element요소만 반환한다. childNodes
는 모든 노드를 반환한다.
const lists = document.querySelector('.nav__links');
console.log(lists.children);
//HTMLCollection(4) [li.nav__item, li.nav__item, li.nav__item, li.nav__item]
console.log(lists.childNodes);
//NodeList(9) [text, li.nav__item, text, li.nav__item, text, li.nav__item, text, li.nav__item, text]
const seciton1FirstNode = document.body.querySelector('#section--1').firstChild;
const section1LastNode = document.body.querySelector('#section--1').lastChild;
console.log(seciton1FirstNode);
console.log(section1LastNode);
각각 해당 노드의 자식 노드중 가장 첫번째 노드 와 마지막 노드를 반환합니다.
const section1 = document.body.querySelector('#section--1');
const section1ParentNode = section1.parentNode;
const section1NextSibling = section1.nextSibling;
const section1PreviousSibling = section1.previousSibling;
console.log(section1ParentNode);
console.log(section1NextSibling);
console.log(section1PreviousSibling);
같은 부모를 가지는 노드들을 형제노드라 부릅니다.
각각 형제 노드 중에서 바로 위에 노드와 바로 아래 노드를 가르켜 반환합니다.
parentNode는 말그대로 부모노드를 반환합니다.
❗️ 여기서 말하는 노드란 DOM에 들어있는 그야말로 모든 요소를 가르킵니다. 일반적인 element도 포함되지만 보통우리가 html구조에서 확인하는 tag 요소가 아닐수 있습니다.
보통 웹페이지를 구성하는 태그의 분신인 element를 다룹니다. node를 다루기 보단 이러한 element를 다루며 웹구성요소를 수정합니다.
이러한 element는 node와 비슷하게 사용이 가능합니다.
const section1 = document.body.querySelector('#section--1');
const section1ParentElement = section1.parentElement;
const section1NextSibling = section1.nextElementSibling;
const section1PreviousSibling = section1.previousElementSibling;
console.log(section1ParentElement);
console.log(section1NextSibling);
console.log(section1PreviousSibling);
id
는 html문서내에서 중복되는 이름이 없어야 합니다. id는 전역객체에 등록되어 사용되어지기 때문입니다. id를 따서 만들어진 전역변수를 이용하여 요소접근에 활용되어서도 안됩니다. 새롭게 할당된 중복된 이름의 변수로 인해 재할당될수 있기 때문입니다.
<div id="elem">
<div id="elem-content">Element</div>
</div>
<script>
// 요소 얻기
let elem = document.getElementById('elem');
// 배경색 변경하기
elem.style.background = 'red';
</script>
//이에 더하여 id 속성값을 그대로 딴 전역 변수를 이용해 접근할 수도 있습니다.
<div id="elem">
<div id="elem-content">Element</div>
</div>
<script>
// 변수 elem은 id가 'elem'인 요소를 참조합니다.
elem.style.background = 'red';
// id가 elem-content인 요소는 중간에 하이픈(-)이 있기 때문에 변수 이름으로 쓸 수 없습니다.
// 이럴 땐 대괄호(`[...]`)를 사용해서 window['elem-content']로 접근하면 됩니다.
</script>
//그런데 이렇게 요소 id를 따서 자동으로 선언된 전역변수는 동일한 이름을 가진 변수가 선언되면 무용지물이 됩니다.
<div id="elem"></div>
<script>
let elem = 5; // elem은 더이상 <div id="elem">를 참조하지 않고 5가 됩니다.
alert(elem); // 5
</script>
자기 자신을 포함해서 인수로 받은 선택자에 해당하는 가장 가까운 조상노드를 찾는다.
const section1 = document.body.querySelector('#section--1');
const section1Title = section1.querySelector('.section__title');
console.log(section1Title.closest('.section'));
// <section class="section" id="section--1"> ... <section>
<div>첫 번째 div</div>
<script>
let divs = document.getElementsByTagName('div');
alert(divs.length); // 1
</script>
<div>두 번째 div</div>
<script>
alert(divs.length); // 2
</script>
<div>첫 번째 div</div>
<script>
let divs = document.querySelectorAll('div');
alert(divs.length); // 1
</script>
<div>두 번째 div</div>
<script>
alert(divs.length); // 1
</script>
위 두개의 코드를 보면 각각 getElementBy* 와 querySelector를 사용합니다. 극명한 차이는 html 문서가 진행되면서 요소의 구성이 실시간으로 바뀌는 와중에 전자는 이미 선언한 변수임에도 불구하고 실시간으로 변경된 내용이 요소에 반영된 반면
후자는 변수가 선언된 당시 프린트 된듯이 이후의 변화는 상관없이 정적인 데이터로 변화가 반영이 안되는 모습입니다.
위 노드들의 관계를 instanceof
키워드로 상속관계를 확인할수 있습니다.
alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true
element.insertAdjacentHTML(position, text);
<div class='testBox'>
<div>
const html = `
<span>Learn the DOM<span>
`;
document.querySelector('.testBox').insertAdjacentHtml('afterbegin', html);
<div class='testBox'>
<span>Learn the DOM<span>
<div>
이렇게 html을 문자열로 입력해서 기존의 DOM에 동적으로 삽입할수 있다.
const content = element.innerHTML;
element.innerHTML = htmlString;
const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alert
el.innerHTML += "<div> 추가한 element입니다.<div>"
위 코드에서 어떠한 element에 접근해서 그내용을 문자열을 이용해서 html을 넣는 방법이다. 다만 추가하는 것이 아니라. 그 내용전체를 초기화하여 새롭게 넣는 방식이다. 보통 초기화로 쓰인다.
+=
으로 추가사항을 더 넣을 수 있으나 이런경우 기존의 내용의 소스를 전부다 로딩시키기 때문에 기존 소스의 로딩 없이 추가하는 방법에 대해서 차후 설명드립니다.
❗️ `innerHTML은 요소 노드에서만 사용이 가능합니다.
노드와 text의 수정에는 이와는 다르게 nodeValue, data 프로퍼티가 있습니다. 실무에서는 둘의 차이가 거의없다고 생각하고 씁니다.<body> 안녕하세요. <!-- 주석 --> <script> let text = document.body.firstChild; alert(text.data); // 안녕하세요.
let comment = text.nextSibling;
alert(comment.data); // 주석
```
💡 요소내의 text만 접근하고 싶을 때에는 textContent프로퍼티를 사용합니다.
<div id="news"> <h1>주요 뉴스!</h1> <p>화성인이 지구를 침공하였습니다!</p> </div>
## outerHtml
innerHtml을 이용하면 소스전체를 바꾸기 때문에 해당소스가 다시 로딩된다고 했습니다. 비슷하지만 다른 outerHtml을 알아봅시다.
```js
const section1 = document.body.querySelector('#section--1');
section1.outerHTML = '<div>Change html? <div>`
// DOM의 보여주기식으로는 바뀜
console.log(section1.outerHTML);
// <section class="section" id="section--1"> ... <section>
// 하지만 저장은 안됨
분명히 내용을 수정을 했는데 기존의 내용이 출력됩니다.
section1.hidden = true;
hidden 이라는 프로퍼티는 기본적으로 style="display:none"과 같은역할을 합니다만 위의 코드가 훨씬 짧습니다.
event.preventDefault()
const loginBtn = document.querySelector('.login__btn');
loginBtn.addEventListener('click', function (e) {
e.preventDefault();
console.log(e, 'where e?');
});
// MouseEvent {isTrusted: true, screenX: 1399, screenY: 125, clientX: 1399, clientY: 46, …} "where e?"
버튼이 submit 버튼이였다면 해당 버튼을 누르는 순간 submit과 연결된 함수로 이어질수 있고 다른 링크로 데이터를 보내는 이벤트가 파생되어 나갈수 있습니다. 하지만 event.preventDefault()를 하면 이벤트가 다른 이벤트로 파생되지 않고 멈춰버립니다. 이렇게 파생 이벤트를 발생시키지 않게 만듭니다.