콘텐츠 대표 이미지 - 자바스크립트, 브라우저를 지배하는 열쇠 🔑Web API 완전 정복 (DOM, BOM, CSSOM)

자바스크립트, 브라우저를 지배하는 열쇠 🔑
Web API 완전 정복 (DOM, BOM, CSSOM)

안녕! 코딩의 세계에 막 발을 들였거나, 혹은 자바스크립트(JavaScript)의 깊은 맛을 더 느끼고 싶은 너에게 이 글을 바쳐. 😎
우리가 자바스크립트를 배울 때, 흔히 '언어' 자체의 문법(변수, 함수, 반복문 같은 거)에 집중하잖아? 근데 그거 알아? 순수한 자바스크립트는 사실 '말'만 할 줄 아는 똑똑한 아기 같은 존재야. 혼자서는 아무것도 못 해. 그림을 그리거나, 창문을 열거나, 가구를 옮기는 능력은 없어.

그럼 우리가 맨날 보는 웹사이트의 화려한 애니메이션, 버튼 클릭하면 뿅 하고 나타나는 팝업창, 스크롤 내리면 스르륵 등장하는 콘텐츠들은 다 어떻게 만드는 걸까?
바로 자바스크립트가 '브라우저'라는 엄청난 능력자를 조종하기 때문이야. 그리고 이 조종을 가능하게 해주는 리모컨이 바로 **Web API**라는 거지.

생각해봐. 네가 엄청난 영화감독(자바스크립트)이야. 네 머릿속엔 기가 막힌 스토리가 있어. 하지만 배우(HTML 요소)들한테 연기 지시를 내리고, 카메라(브라우저 창) 앵글을 바꾸고, 조명(CSS 스타일)을 조절하려면 '확성기'나 '무전기' 같은 도구가 필요하겠지? Web API가 바로 그 역할을 해.

오늘은 이 Web API의 핵심 삼대장, **DOM, BOM, CSSOM**에 대해 아주 탈탈 털어볼 거야. 이 세 가지만 제대로 이해해도, 넌 더 이상 자바스크립트 '문법'만 아는 초보가 아니라, 브라우저라는 거대한 무대를 자유자재로 주무르는 '연출가'가 될 수 있어. 준비됐어? 그럼, 지금부터 시작하자!

DOM: 웹 페이지의 설계도를 펼쳐라! 📜

첫 번째 주인공은 **DOM(Document Object Model)**이야. 번역하면 '문서 객체 모델'. 이름부터 뭔가 딱딱하지?
쉽게 말해줄게. 우리가 보는 웹 페이지는 사실 ``, `

`, `
` 같은 태그들로 이루어진 그냥 텍스트 파일이야. 브라우저는 이 텍스트 파일을 읽어서 우리가 눈으로 볼 수 있는 화면으로 그려주지.

이때 브라우저는 그냥 텍스트를 그림으로 뿅! 하고 바꾸는 게 아니야. 먼저 이 HTML 문서를 **자바스크립트가 이해할 수 있는 '객체(Object)' 형태로 바꿔서 메모리에 저장**해. 마치 레고 설명서를 보고, 하나하나 부품을 조립해서 거대한 모델을 만드는 것처럼 말이야. 이렇게 만들어진 객체 모델이 바로 DOM이야.

DOM은 '트리(Tree)' 구조를 하고 있어. 가장 꼭대기에 `document` 객체가 있고, 그 아래로 ``, `

`, `
`, `

` 같은 태그들이 나뭇가지처럼 뻗어 나가는 형태지. 모든 HTML 태그, 태그 안의 텍스트, 심지어 주석까지 전부 각각의 '노드(Node)'라는 객체가 되어서 이 트리를 구성해.

HTML 코드와 DOM 트리 시각화 Web API의 핵심: 브라우저가 코드를 이해하는 방식 HTML Code <body> <h1> 안녕! </h1> <div> <p> DOM이야 </p> </div> </body> DOM Tree body h1 div "안녕!" p "DOM이야"

자, 그럼 이 DOM으로 뭘 할 수 있을까? **거의 모든 것.**
자바스크립트는 이 DOM API를 통해 웹 페이지의 모든 요소에 접근하고, 내용을 바꾸고, 스타일을 입히고, 심지어 없던 요소를 새로 만들거나 기존 요소를 삭제할 수도 있어. 우리가 아는 '동적인 웹'은 전부 이 DOM 조작을 통해 이루어지는 거야.

1. DOM 요소에 접근하기 (feat. 쿼리)

DOM을 조작하려면 먼저 원하는 요소를 '선택'해야겠지? 마치 리모컨으로 특정 채널을 고르는 것처럼 말이야. 자바스크립트는 다양한 방법을 제공해.

`document.getElementById('아이디')`
가장 클래식하고 빠른 방법. HTML에서 `id` 속성은 페이지 내에서 유일해야 하거든. 그래서 특정 요소 하나를 콕 집어서 가져올 때 아주 유용해.

// HTML: <div id="hero">나는 영웅!</div>
const heroElement = document.getElementById('hero');
console.log(heroElement); // <div id="hero">...</div> 객체를 반환

`document.getElementsByClassName('클래스명')`
이름에 'Elements'(복수형)가 들어간 걸 주목해. 클래스는 여러 요소가 함께 가질 수 있잖아? 그래서 이 메서드는 해당 클래스를 가진 모든 요소를 `HTMLCollection`이라는 *유사 배열*에 담아서 반환해.

// HTML: <p class="item">항목1</p>, <p class="item">항목2</p>
const items = document.getElementsByClassName('item');
console.log(items); // HTMLCollection [p.item, p.item]
console.log(items[0]); // 첫 번째 p 태그에 접근 가능
💡 잠깐, `HTMLCollection`이 뭐지? `HTMLCollection`은 살아있는(live) 컬렉션이야. 이게 무슨 말이냐면, 자바스크립트로 DOM에 변화를 주면(예: 새로운 `item` 클래스 요소를 추가하면), `items` 변수를 다시 조회하지 않아도 **자동으로 변경사항이 반영**된다는 뜻이야. 신기하지? 하지만 배열의 `forEach` 같은 최신 메서드는 쓸 수 없어서 약간 불편할 때도 있어.

`document.querySelector('CSS 선택자')`
현대 자바스크립트의 구세주! CSS 선택자 문법을 그대로 쓸 수 있어서 정말 강력하고 편리해. `#id`, `.class`, `div > p` 같은 복잡한 선택도 가능하지. 단, 조건에 맞는 **첫 번째 요소 하나만** 반환해.

const firstItem = document.querySelector('.item'); // 클래스가 'item'인 첫 번째 요소
const heroAgain = document.querySelector('#hero'); // 아이디가 'hero'인 요소

`document.querySelectorAll('CSS 선택자')`
`querySelector`의 형제. 이름처럼 조건에 맞는 **모든 요소를 `NodeList`**라는 유사 배열에 담아서 반환해. `getElementsByClassName`과 비슷하지만, `NodeList`는 대부분의 경우 '살아있지 않은(static)' 컬렉션이야. 즉, DOM이 바뀌어도 `NodeList` 변수의 내용은 자동으로 업데이트되지 않아. 대신 `forEach`를 쓸 수 있어서 훨씬 편해!

const allItems = document.querySelectorAll('.item');
allItems.forEach(item => {
  console.log(item.textContent);
});
// 출력:
// 항목1
// 항목2

2. DOM 조작하기 (feat. 창조와 변화)

자, 이제 원하는 요소를 선택했으니 마음껏 요리해볼 시간이야.

요소 생성 및 추가
없던 HTML 요소를 자바스크립트로 만들어서 페이지에 붙일 수 있어.

// 1. 새로운 <li> 요소를 만든다.
const newLi = document.createElement('li');

// 2. 텍스트를 추가한다.
newLi.textContent = '새로운 항목!';

// 3. 페이지의 <ul> 태그를 찾아서...
const ul = document.querySelector('ul');

// 4. 그 자식으로 <li>를 추가한다.
ul.appendChild(newLi);

내용 변경: `innerHTML` vs `textContent` vs `innerText`
이 셋은 정말 중요해서 꼭 구분해야 해.

  • `innerHTML`: HTML 태그를 **인식**해서 내용을 갈아 끼워. 편리하지만, 사용자가 입력한 값을 그대로 넣으면 `script` 태그 등을 이용한 **XSS(Cross-Site Scripting) 공격**에 취약해질 수 있으니 조심해야 해.
  • `textContent`: 순수하게 **텍스트**만 다뤄. HTML 태그를 넣어도 그냥 문자열로 취급해버리지. 빠르고 안전해서 텍스트만 바꿀 땐 이게 최고야.
  • `innerText`: `textContent`랑 비슷한데, 약간 더 똑똑해. 화면에 **보이는(rendered)** 텍스트만 가져와. 예를 들어 `display: none;`으로 숨겨진 텍스트는 가져오지 않아. 이 '계산' 과정 때문에 `textContent`보다 조금 느릴 수 있어.
const myDiv = document.querySelector('#my-div');

myDiv.innerHTML = '<strong>중요!</strong>'; // 화면에 '중요!'가 굵게 표시됨
myDiv.textContent = '<strong>중요!</strong>'; // 화면에 '<strong>중요!</strong>' 글자가 그대로 보임

속성(Attribute)과 스타일 변경
`setAttribute`, `getAttribute`로 HTML 속성을 바꾸거나, `element.style` 프로퍼티로 CSS 스타일을 직접 바꿀 수 있어.

const link = document.querySelector('a');

// href 속성 변경
link.setAttribute('href', 'https://www.jaenung.net');

// style 속성을 직접 조작 (인라인 스타일로 들어감)
link.style.color = 'blue';
link.style.fontSize = '20px'; // CSS의 font-size는 JS에서 카멜케이스(camelCase)로!

3. DOM 이벤트: 사용자와의 소통

DOM의 꽃은 바로 **이벤트(Event)**야. 사용자가 버튼을 클릭하거나, 마우스를 움직이거나, 키보드를 누르는 모든 행동이 '이벤트'가 되어 자바스크립트에 신호를 보내. 우리는 이 신호를 받아서 특정 동작을 실행시킬 수 있지.

`addEventListener`
이벤트 처리는 `element.onclick = function() {}` 처럼 할 수도 있지만, 현대적인 방식은 `addEventListener`를 쓰는 거야. 하나의 이벤트(예: 'click')에 여러 개의 함수를 연결할 수 있는 등 훨씬 유연하거든.

const myButton = document.querySelector('#my-button');

function handleClick() {
  alert('버튼이 클릭되었어요!');
}

myButton.addEventListener('click', handleClick);
🚨 성능 이야기: Reflow와 Repaint DOM을 너무 자주, 생각 없이 바꾸면 웹 페이지 성능이 뚝뚝 떨어질 수 있어. 브라우저는 DOM에 변화가 생기면 화면을 다시 그려야 하는데, 이때 두 가지 과정이 발생해.

- **Reflow(리플로우) 또는 Layout(레이아웃):** 요소의 크기나 위치 같은 기하학적인 속성이 바뀌었을 때 발생해. 레이아웃 계산을 다시 해야 해서 비용이 아주 비싸. 예를 들어, 요소의 `width`를 바꾸거나, DOM에 요소를 추가/삭제하는 경우.
- **Repaint(리페인트) 또는 Redraw(리드로우):** 레이아웃에 영향을 주지 않는, 색상이나 배경 같은 시각적 스타일만 바뀔 때 발생해. Reflow보다는 부담이 적어.

똑똑한 개발자는 이 Reflow를 최소화하려고 노력해. 예를 들어, 여러 스타일을 한 번에 바꾸고 싶을 때, `element.style.left`, `element.style.top`을 각각 바꾸는 것보다, 미리 정의된 클래스를 한 번에 `element.classList.add('new-position')` 하는 식으로 바꾸는 게 훨씬 효율적이야.

BOM: 브라우저 자체를 조종하는 마법 🪄

DOM이 웹 페이지 '내용'의 설계도였다면, **BOM(Browser Object Model)**은 그 페이지를 담고 있는 '브라우저 창' 자체를 조종하는 리모컨이야.

BOM은 DOM처럼 표준화된 명세가 없어. 그래서 브라우저마다 조금씩 다를 수 있지만, 대부분 공통적으로 지원하는 핵심 객체들이 있어. 그리고 이 모든 BOM 객체들의 정점에는 `window` 객체가 있어. 사실 우리가 `alert()`, `setTimeout()` 같은 함수들을 `window.` 없이 그냥 쓰는 이유도, `window`가 브라우저 환경의 **전역 객체(Global Object)**이기 때문이야. `document` 객체도 사실은 `window.document`인 셈이지.

1. `window` 객체: 모든 것의 시작

`window` 객체는 현재 브라우저 창이나 탭 그 자체를 의미해. 창의 크기를 얻거나, 새 창을 띄우거나, 경고창을 보여주는 등 다양한 일을 할 수 있어.

// 현재 창의 내부 너비와 높이
console.log(window.innerWidth, window.innerHeight);

// 경고창, 확인창, 입력창
alert('경고!');
const isOkay = confirm('계속할까요?');
const userName = prompt('이름을 입력하세요');

// 새 창 열기
window.open('https://www.google.com');

2. `location` 객체: URL의 모든 것

현재 페이지의 URL 정보를 담고 있고, 페이지를 이동시키는 기능도 해.

// 현재 페이지의 전체 URL
console.log(location.href);

// 다른 페이지로 이동하기
location.href = 'https://www.naver.com';
// location.assign('https://www.naver.com'); // 위와 동일

// 페이지 새로고침
location.reload();

// 히스토리에 남기지 않고 페이지 이동 (뒤로가기 불가)
// location.replace('https://www.naver.com');

3. `history` 객체: 시간 여행자

브라우저의 방문 기록(세션 히스토리)을 조작할 수 있어. 보안상의 이유로 정확히 어떤 URL을 방문했는지는 알 수 없고, 앞/뒤로 이동만 가능해.

// 뒤로 가기
history.back();

// 앞으로 가기
history.forward();

// 2페이지 뒤로 가기
history.go(-2);

4. `navigator` 객체: 브라우저 탐정

사용자가 어떤 브라우저를 쓰는지, 어떤 언어를 설정했는지, 온라인 상태인지 등의 정보를 담고 있어.

// 브라우저 정보가 담긴 User Agent 문자열
console.log(navigator.userAgent);

// 브라우저 기본 언어
console.log(navigator.language);

// 온라인 상태 확인
console.log(navigator.onLine); // true 또는 false

5. 타이머: 시간의 마법사

BOM의 일부인 타이머 함수들은 비동기 프로그래밍의 기초를 이루는 아주 중요한 기능이야.

  • `setTimeout(함수, 시간)`: 지정된 시간(밀리초) 후에 함수를 **한 번** 실행해.
  • `setInterval(함수, 시간)`: 지정된 시간마다 함수를 **계속 반복**해서 실행해.
  • `clearTimeout(타이머ID)`, `clearInterval(타이머ID)`: 예약된 타이머를 취소해.
console.log('시작!');

const timerId = setTimeout(() => {
  console.log('3초가 지났습니다!');
}, 3000);

// 만약 3초가 되기 전에 특정 조건이 만족되면 타이머를 취소할 수 있어.
// clearTimeout(timerId);

let count = 0;
const intervalId = setInterval(() => {
  count++;
  console.log(`1초마다 실행: ${count}번째`);
  if (count >= 5) {
    clearInterval(intervalId); // 5번 실행 후 반복을 멈춤
    console.log('인터벌 종료!');
  }
}, 1000);

CSSOM: 스타일의 비밀을 파헤치다 🎨

자, 이제 마지막 주인공 **CSSOM(CSS Object Model)**이야. 이름에서 감이 오지? DOM이 HTML을 객체로 만든 거라면, CSSOM은 CSS를 객체로 만든 거야.

브라우저는 CSS 파일이나 ` 핵심 렌더링 경로 (Critical Rendering Path) HTML DOM Tree CSS CSSOM Tree Render Tree Layout Paint Display

이 과정을 이해하면 왜 CSS를 `

` 태그 안에 넣고, 자바스크립트를 `` 태그가 끝나기 직전에 넣으라고 하는지 알 수 있어. 브라우저가 렌더 트리를 최대한 빨리 만들어서 사용자에게 일단 화면을 보여주고, 그 다음에 스크립트 기능을 로드하는 게 사용자 경험에 더 좋기 때문이지.

DOM, BOM, CSSOM: 환상의 오케스트라 🎶

지금까지 우리는 DOM, BOM, CSSOM이라는 세 명의 연주자를 각각 만나봤어. 하지만 이들의 진정한 힘은 함께 연주할 때 나와. 마치 오케스트라처럼 말이야.

  • **BOM**은 지휘자(`window`)가 전체 공연장(브라우저)을 통제하고, 공연 시간을 조율(`setTimeout`)하는 것과 같아.
  • **DOM**은 무대 위의 배우(HTML 요소)들이 대본(DOM Tree)에 따라 움직이고, 소품을 사용하고, 서로 상호작용하는 것과 같지.
  • **CSSOM**은 조명팀이 배우들에게 스포트라이트(스타일)를 비추고, 무대 배경을 바꾸는 것과 같아.

이 모든 것이 자바스크립트라는 총감독의 지휘 아래 완벽한 하모니를 이루면서 우리가 경험하는 '인터랙티브 웹'이라는 멋진 공연이 탄생하는 거야.

실전 예제: 다크 모드 토글 버튼 만들기

이 모든 개념을 한 번에 이해할 수 있는 최고의 예제는 바로 '다크 모드' 기능이야. 한번 상상하며 따라와 봐.

<!-- HTML -->
<body>
  <h1>안녕하세요!</h1>
  <p>라이트 모드입니다.</p>
  <button id="theme-toggle">다크 모드로 변경</button>
</body>
/* CSS */
body {
  transition: background-color 0.3s, color 0.3s;
}

body.dark-mode {
  background-color: #121212;
  color: #ffffff;
}

body.dark-mode p {
  color: #bbbbbb;
}
// JavaScript
// 1. 필요한 DOM 요소들을 선택한다. (DOM API)
const body = document.querySelector('body');
const toggleButton = document.querySelector('#theme-toggle');
const pTag = document.querySelector('p');

// 2. 버튼 클릭 이벤트를 감지한다. (DOM Event)
toggleButton.addEventListener('click', () => {
  // 3. body에 'dark-mode' 클래스가 있는지 확인하고, 없으면 추가, 있으면 제거한다. (DOM 조작)
  body.classList.toggle('dark-mode');

  // 4. 현재 상태에 따라 버튼 텍스트와 p 태그 내용을 변경한다. (BOM, DOM 조작)
  const isDarkMode = body.classList.contains('dark-mode');
  
  if (isDarkMode) {
    toggleButton.textContent = '라이트 모드로 변경';
    pTag.textContent = '다크 모드입니다.';
    // 로컬 스토리지에 상태 저장 (BOM - Web Storage API)
    localStorage.setItem('theme', 'dark');
  } else {
    toggleButton.textContent = '다크 모드로 변경';
    pTag.textContent = '라이트 모드입니다.';
    localStorage.setItem('theme', 'light');
  }
});

// 5. 페이지 로드 시 저장된 테마를 적용한다. (BOM, DOM)
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
    // 가상으로 클릭 이벤트를 발생시켜 상태를 동기화
    toggleButton.click();
}

이 간단한 예제 안에 우리가 배운 모든 것이 녹아있어.

  • **DOM:** `querySelector`, `addEventListener`, `classList.toggle`, `textContent`를 사용해서 페이지의 구조와 내용을 바꿨어.
  • **BOM:** `localStorage`라는 Web Storage API(BOM의 일부로 취급되기도 함)를 사용해서 사용자의 테마 선택을 브라우저에 저장했지. 덕분에 페이지를 새로고침해도 다크 모드가 유지돼.
  • **CSSOM:** 자바스크립트가 `dark-mode` 클래스를 ``에 추가하자, 브라우저는 이 변화를 감지하고 CSSOM을 업데이트했어. 그리고 새로운 스타일 규칙(`body.dark-mode`)을 렌더 트리에 적용해서 화면을 다시 그렸지(Repaint).

마치며: 기본기의 중요성

요즘은 React, Vue, Svelte 같은 멋진 프레임워크들이 많아서 우리가 직접 DOM을 조작할 일이 예전보다 줄어든 건 사실이야. 이 프레임워크들은 '가상 DOM(Virtual DOM)' 같은 기술을 써서 더 효율적으로 화면을 업데이트해주거든.

하지만! 이 프레임워크들도 결국 내부에서는 오늘 우리가 배운 DOM, BOM, CSSOM API를 사용해서 브라우저와 소통해. 그래서 이 기본 원리를 이해하는 건 정말 중요해. 자동차 운전법을 배웠다고 해서 자동차 엔진의 원리를 알 필요는 없지만, 카레이서가 되려면 엔진을 이해해야 하는 것과 같아. 웹 성능을 최적화하거나, 복잡한 버그를 디버깅하거나, 프레임워크 없이 순수한 자바스크립트로 무언가를 만들어야 할 때, 이 지식은 너의 가장 강력한 무기가 될 거야.

이런 깊이 있는 지식을 바탕으로 자신만의 웹 프로젝트를 만들어보거나, **재능넷** 같은 플랫폼에서 여러분의 개발 실력을 필요로 하는 사람들을 도와주며 경험을 쌓아보는 건 어떨까? 이론을 실제로 적용해보는 것만큼 좋은 공부는 없으니까 말이야.

오늘 정말 긴 여정이었지만, 여기까지 따라온 너는 이제 자바스크립트로 브라우저를 지배할 수 있는 열쇠를 손에 쥔 거야. 축하해! 앞으로 너의 코드가 만들어낼 놀라운 웹의 세계를 기대할게. 화이팅! 🚀

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.