웹 브라우저는 움직이는 부분이 매우 많은 복잡한 소프트웨어이며, 그 중 많은 부분은 웹 개발자가 자바스크립트로 조작할 수 없다.
이러한 제한이 나쁘다고 생각할 수 있지만, 대부분 보안문제를 방지하고자 잠겨있다.
웹 사이트에 저장된 암호나 기타 중요한 정보에 액세스하여 타인이 마치 본인처럼 웹 사이트에 로그인할 수 있다고 생각해보면 이해가 갈 것이다.
이러한 제한들이 있지만, 웹 API는 여전히 우리에게 웹 페이지로 많은 것을 할 수 있게 해주는 기능들을 제공한다.
window는 웹 페이지가 로드되는 브라우저 탭이며, 자바스크립트에서 window
객체로 표시된다.
이 객체에서 사용할 수 있는 메서드를 통해 window의 크기를 조작하고 (window.innerWidth
, window.innerHeight
) 페이지에 해당하는 데이터를 클라이언트 측에 저장하고, 이벤트 핸들러를 현재 창에 등록하는 ( addEventListener()
는 EventTarget
객체의 메서드인데, window
객체는 EventTarget
객체를 상속한다) 등의 작업을 수행할 수 있다.
navigator는 브라우저( 즉, user agent )의 상태와 ID를 나타낸다.
JavaScript에서는 Navigatior
객체로 표현된다.
이 객체를 이용하여 사용자의 국가별 언어, 웹 캠의 미디어 스트림 등을 검색할 수 있다.
document는 window에 로드된 실제 페이지이며, JavaScript에서 Document
로 표현된다.
이 객체를 사용하여 문서를 구성하는 HTML, CSS에 대한 정보를 반환하고 조작할 수 있다.
예를 들어 DOM의 엘리먼트에 대한 참조를 가져오거나, 텍스트 내용을 변경하거나, 새 스타일을 적용하거나, 새 엘리먼트를 만들고 하위로 현재 엘리먼트에 추가하거나 문서를 모두 삭제할 수 있다.
브라우저의 각 탭에 현재 로드된 문서는 DOM으로 표시된다.
DOM은 자바스크립트로 HTML구조에 보다 쉽게 접근할 수 있게 브라우저에 의해 만들어진 "트리 구조" 이다.
아래의 예제는 HTML문서와 그에 매칭되는 DOM구조이다.
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>Simple DOM example</title>
</head>
<body>
<section>
<img
src="dinosaur.png"
alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth." />
<p>
Here we will add a link to the
<a href="https://www.mozilla.org/">Mozilla homepage</a>
</p>
</section>
</body>
</html>
DOM 트리 다이어그램을 보고 싶다면 Ian Hickson의 Live DOM viewer➡️에서 확인하자.
트리의 각 항목을 노드라고 부른다.
위의 다이어그램에서 일부 노드는 엘리먼트( HTML
, HEAD
, META
)를 나타내고 또 다른 일부 노드는 텍스트( #text
)를 나타내는 것을 볼 수 있다.
더 많은 타입➡️이 있지만 가장 중요한 것은 위의 것들이다.
노드는 트리에서 다른 노드와의 위치로도 참조된다.
Root node : 트리의 맨 위 노드이다.
HTML의 경우 항상 HTML노드이다.
SVG나 XML과 같은 다른 마크업 언어일 경우엔 다른 Root node를 가진다.
Child node : 다른 노드의 바로 밑에 포함된 노드이다.
Descendant node : 다른 노드에 포함된 노드이다.
몇 단계 중첩으로 포함되어 있는지는 중요치 않다.
Parent node : 다른 노드를 포함하고 있는 노드이다.
Sibling nodes : DOM tree에서 같은 레벨에 있는 노드들이다.
DOM을 사용하기 전에 위의 용어들을 숙지하는 것이 유용하다.
<section>
<img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth.">
<p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla homepage</a></p>
</section>
DOM 내부에서 엘리먼트를 조작하려면 먼저 엘리먼트를 선택하고 변수 내부에 참조를 저장해야 한다.
const link = document.querySelector('a');
변수에 엘리먼트의 참조를 저장했으니, 프로퍼티와 메소드를 이용해 조작할 수 있다.
<a>
엘리먼트의 경우, 프로퍼티와 메소드는 HTMLAnchorElement
에 정의되어 있다.
HTMLAnchorElement는 HTMLElement, Node 객체와 같이 부모의 인터페이스도 이용할 수 있다.
내부의 텍스트를 변경하기 위해서 Node.textContent
속성을 사용한다.
link.textContent = 'Mozilla Developer Network';
엘리먼트의 어트리뷰트의 변경도 다음과 같이 할 수 있다.
link.href = 'https://developer.mozilla.org';
자바스크립트에는 엘리먼트를 선택하고 변수에 참조를 저장하는 방법이 여러개가 있는 데, Document.querySelector()
는 권장되는 최신 접근 방법이다.
CSS selector를 이용해 엘리먼트를 선택할 수 있어서 편리하다.
매칭 되는 엘리먼트가 여러개일 경우엔, 첫 번째 엘리먼트가 선택된다.
만약 여러개의 엘리먼트를 선택하고 싶다면 Document.querySelectorAll()
를 사용하자.
일치하는 모든 엘리먼트들을 담은 NodeList
라는 유사 배열 객체를 반환한다.
NodeList
는 특이하게 Live NodeLists와 Static NodeLists로 분류된다.
Node.childNodes
에서 반환된 NodeList는 Live 이고Document.querySelectorAll()
에서 반환된 NodeList는 Static이다.
엘리먼트를 참조하는 레거시 방법으로는 Document.getElementById()
, Document.getElementsByTagName()
등이 있다.
다시 위의 예제 HTML 코드로 돌아가서, <section>
엘리먼트 안에 새로운 엘리먼트를 추가해보자.
그러기 위해 먼저 <section>
엘리먼트의 참조를 변수에 저장한다.
const sect = document.querySelector('section');
새 엘리먼트를 생성하기 위해서 Document.createElement()
를 사용한다.
const para = document.createElement('p');
para.textContent = 'We hope you enjoyed the ride.';
새로 만든 엘리먼트를 <section>
에 포함시키 위해 Node.appendChild()
를 사용한다.
sect.appendChild(para);
텍스트 노드를 만들고 추가하기 위해서는 다음과 같이 한다.
const text = document.createTextNode(' — the premier source for web development knowledge.');
const linkPara = document.querySelector('p');
linkPara.appendChild(text);
또는 Element.append()
를 사용하면 매개변수로 전달한 텍스트를 자동으로 텍스트 노드로 만들어서 추가해준다.
const linkPara = document.querySelector('p');
linkPara.append(' — the premier source for web development knowledge.');
Node.appendChild()
로 자식 엘리먼트의 순서도 바꿀 수 있다.
<ul>
<li id="first">first</li>
<li id="second">second</li>
<li id="third">third</li>
</ul>
const ul = document.querySelector("ul");
const first = document.querySelector("#first");
ul.appendChild(first);
위의 코드처럼 기존에 있던 자식 엘리먼트를 대상으로 Node.appendChild()
를 하면 다음과 같이 순서가 변경된다.
<ul>
<li id="second">second</li>
<li id="third">third</li>
<li id="first">first</li>
</ul>
이는 복사본을 통해 이루어 지지 않는다.
first 변수는 <li id="first">first</li>
를 유일하게 참조한다.
만약 Node를 복사하고 싶다면 Node.cloneNode()
를 사용해야 한다.
노드를 제거하고 싶다면 Node.removeChild()
또는 Element.remove()
를 사용하자.
부모와 자식 모두 참조하고 있으면 Node.removeChild()
를 사용할 수 있고 자기 자신만을 참조하고 있으면 Element.remove()
를 사용할 수 있다.
단, Element.remove()
는 최신버전의 브라우저에서만 동작하므로 주의해야 한다.
자바스크립트를 통해 CSS 스타일을 다양한 방식으로 조작할 수 있다.
Document.stylesheets
를 사용하여 문서에 첨부된 모든 스타일시트의 목록을 얻을 수 있다.
이 목록은 CSSStyleSheet
객체가 담겨있는 유사 배열 객체를 반환한다.
그 후 원하는 대로 스타일을 추가 / 제거할 수 있다.
그러나 이러한 기능은 다소 오래되고 어려운 방법이기 때문에 더 배울 필요가 없다.
훨씬 쉬운 방법들이 있기 때문이다.
첫 번째 방법은 스타일을 지정할 엘리먼트에 동적으로 인라인 스타일을 직접 추가하는 것이다.
이 작업은 문서의 각 엘리먼트에 대한 인라인 스타일 정보를 포함하고 있는 HTMLElement.style
에서 수행된다.
이 객체의 속성을 직접 설정해서 엘리먼트 스타일을 업데이트할 수 있다.
para.style.color = 'white';
para.style.backgroundColor = 'black';
para.style.padding = '10px';
para.style.width = '250px';
para.style.textAlign = 'center';
위와 같이 설정하고 새로고침을 한 뒤에 개발자 도구에서 HTML을 보자.
<p
style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">
We hope you enjoyed the ride.
</p>
엘리먼트의 스타일 어트리뷰트에 인라인 스타일이 지정되어있는 모습을 확인 할 수 있다.
문서의 스타일을 동적으로 조작할 수 있는 또 다른 일반적인 방법이 있다.
특정한 클래스 selector에 미리 스타일을 지정해 놓고 자바스크립트로 해당 클래스를 설정해주는 방식이다.
.highlight {
color: white;
background-color: black;
padding: 10px;
width: 250px;
text-align: center;
}
위와 같이 CSS에 highlight라는 클래스 선택자의 스타일을 설정했다.
para.setAttribute('class', 'highlight');
엘리먼트에 클래스를 할당할 수 있는 여러 방법이 있지만 여기서는 두루두루 사용되는 Element.setAttribute()
를 사용했다.
새로고침을 하면 스타일이 적용되는 걸 확인 할 수 있다.
어떤 방법을 선택하느냐는 개발자 본인에게 달려있다.
둘 다 장단점이 있다.
첫 번째 방법은 간단한 사용에 적합한 반면, 두 번째 방법은 더 "순수"하다.
MDN 문서에서 따로 어떠한 방법을 권장한다라고 나와있지는 않지만 개인적으로 두 번째 방법을 사용하는 것이 옳다고 생각한다.
CSS의 우선순위 때문이다.
CSS는 selector의 specificity와 CSS가 포함된 위치 즉, 인라인, 내부, 외부 등 위치에 따라 적용 우선순위가 달라진다.
최근에는 layer라는 개념도 추가되어서 더욱 우선순위가 헷갈릴 수 있다.
그래서 BEM과 같이 specificity를 동일하게 해주는 명명법 기술도 등장하는데, 이처럼 인라인 스타일을 입히는 방식을 사용하면 마치 !important
를 여기저기 남발하는 것과 같은 느낌이다.
[참고] : MDN