브라우저 환경과 다양한 명세서

원래 자바스크립트는 웹 브라우저에서 사용하려고 만든 언어이나 이후 다양한 진화를 거쳐 다양한 플렛폼에서 사용하게 되었습니다. 자바스크립트가 돌아가는 플랫폼을 호스트라고 부릅니다. 호스트는 브라우저, 웹서버, 등등 다양한 플렛폼이 될수 있습니다. 호스트 환경은 랭귀지 코어(ECMAScript)에 더하여 플렛폼에 특정되는 다양한 객체와 함수를 제공합니다.

Node.js는 서버 사이드기능을 제공하며,웹페이지는 웹페이지를 제어하기 위한 수단을 제공합니다.

호스트환경이 웹브라우저일 때의 사용할 수 있는기능을 개괄적으로 보여줍니다.

window 객체

최상단에 위치한 window 객체는 두가지 역할을 합니다.

  • 자바스크립트의 전역객체
  • 브라우저 창(browser window)를 대변하고, 이를 제어할 수 있는 메서드를 제공합니다.
function sayHi() {
  alert("안녕하세요.");
}

// 전역 함수는 전역 객체(window)의 메서드임
window.sayHi();

alert(window.innerHeight); // 창 내부(inner window) 높이

문서객체모델 DOM

문서객체모델 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의 자동교정

DOM의 생성과정에서는 짝이 안맞는 태그등을 잡아서 생성시켜주고 최대한 HTML을 정상적으로 교정하여 HTML문서를 생성하고 DOM트리를 생성합니다.

DOM의 동작원리

HTML의 모든것은 DOM에 추가됩니다. 그것이 주석이든 무엇이든 간에 DOM에 추가됩니다. 노드 타입은 총 12가지 이나 실무에서는 주로 4가지를 다룹니다.

  • DOM의 '진입점"이 되는 문서(document) 노드
  • HTML 태그에서 만들어지며, DOM 트리를 구성하는 블록인 요소 노드(element node)
  • 텍스트를 포함하는 텍스트 노드(text node)
  • 화면에 보이지는 않지만, 정보를 기록하고 자바스크립트를 사용해 이 정보를 DOM으로부터 읽을 수 있는 주석(comment) 노드

DOM 노드 탐색

  • document.documentElement : document를 제외하고 html문서의 가장 최상단에 존재합니다.
  • document.body : <body> document.body가 존재하지 않을 수도있습니다.
<html>
<head>
  <script>
    alert( "HEAD: " + document.body ); // null, 아직 <body>에 해당하는 노드가 생성되지 않았음
  </script>
</head>
<body>
  <script>
    alert( "BODY: " + document.body ); // HTMLBodyElement, 지금은 노드가 존재하므로 읽을 수 있음
  </script>
</body>
</html>
  • document.head : <head>에 해당하는 노드입니다.

자식 노드 탐색

  • 자식 노드(chilren, child node)는 바로 아래의 자식노드를 나타냅니다. 자식 노드는 부모 노드의 바로 아래의 노드입니다.
    <head>,<body><html>의 바로 아래의 노드입니다.

  • 후손 노드는 자식관계에서 직계로 내려오는 관계에 놓인 노드를 의미합니다.

.childNodes

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에는 거의 쓰이지 않는 프로퍼티가 있어 이것 까지 모두 순회하기 때문입니다.

childen

얼핏 보면 위의 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]

.firstChild , .lastChild

const seciton1FirstNode = document.body.querySelector('#section--1').firstChild;
const section1LastNode = document.body.querySelector('#section--1').lastChild;
console.log(seciton1FirstNode);
console.log(section1LastNode);

각각 해당 노드의 자식 노드중 가장 첫번째 노드 와 마지막 노드를 반환합니다.

.previousSibling, .nextSibling, .parentNode

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는 말그대로 부모노드를 반환합니다.

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • previousSibling
  • nextSibling

❗️ 여기서 말하는 노드란 DOM에 들어있는 그야말로 모든 요소를 가르킵니다. 일반적인 element도 포함되지만 보통우리가 html구조에서 확인하는 tag 요소가 아닐수 있습니다.

element

보통 웹페이지를 구성하는 태그의 분신인 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);
  • parentElement
  • children
  • firstElementChild
  • lastElementChild,
  • previousElementSibling
  • nextElementSibling

getElementbyId

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>

closest

자기 자신을 포함해서 인수로 받은 선택자에 해당하는 가장 가까운 조상노드를 찾는다.

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>

getElementsBy* & querySelector

<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 문서가 진행되면서 요소의 구성이 실시간으로 바뀌는 와중에 전자는 이미 선언한 변수임에도 불구하고 실시간으로 변경된 내용이 요소에 반영된 반면

후자는 변수가 선언된 당시 프린트 된듯이 이후의 변화는 상관없이 정적인 데이터로 변화가 반영이 안되는 모습입니다.

Summary


위 노드들의 관계를 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

DOM 수정

insertAdjacentHTML

element.insertAdjacentHTML(position, text);

insertAdjacentHTML mdn

<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에 동적으로 삽입할수 있다.

innerHTML

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.outerHTML은 section1자리에 있던 DOM을 해당 내용으로 교체합니다만 DOM을 저장하지는 않습니다. 즉, 일시적인 보여주기식 교체에 불과합니다.
  • 저장은 되지 않았기에 console.log(section1.outerHTML)을 출력하면 본 내용이 나옵니다. 즉, 수정하려던 내용은 어디에도 저장되어 있지 않습니다.

hidden

section1.hidden = true;

hidden 이라는 프로퍼티는 기본적으로 style="display:none"과 같은역할을 합니다만 위의 코드가 훨씬 짧습니다.

e.preventDefault()

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()를 하면 이벤트가 다른 이벤트로 파생되지 않고 멈춰버립니다. 이렇게 파생 이벤트를 발생시키지 않게 만듭니다.

profile
일상을 기록하는 삶을 사는 개발자 ✒️ #front_end 💻

0개의 댓글