안녕하세요. IT 엘도라도 에 오신 것을 환영합니다.
글을 쓰는 것은 귀찮지만 다시 찾아보는 것은 더 귀찮습니다.
완전한 나만의 것으로 만들기 위해 지식을 차곡차곡 저장해 보아요.   포스팅 둘러보기 ▼

자바스크립트 (JavaScript)

[JavaScript] DOM/BOM, Web API, 이벤트 캡쳐링/버블링

피그브라더 2020. 5. 28. 11:25

BOM과 DOM의 핵심적인 개념은 그 이름에 담겨 있다. BOM은 Brower Object Model, DOM은 Document Object Model이다. Object Model이란 객체 지향 모델, 즉 무언가를 객체(Object)들로서 표현하는 형식을 일컫는다. 따라서 BOM은 웹 브라우저 자체를 객체들로 표현하는 형식을 말하며, DOM은 웹 페이지(문서)의 내용을 객체들로 표현하는 형식을 말한다. 무엇보다 중요한 둘의 공통점은 바로 JavaScript와 같은 스크립트 언어가 웹 브라우저/페이지를 쉽게 제어 및 조작할 수 있도록 한다는 것이다. 이번 포스팅에서는 BOM과 DOM의 개념을 간단히 정리하고, 이와 관련된 이벤트 캡쳐링 및 버블링의 개념도 한 번 알아보도록 하자.

 

1. DOM (Document Object Model)

DOM은 객체 지향 모델로서 구조화된 문서를 표현하는 형식을 말한다. 쉽게 말해서, HTML 등의 문서의 내용을 트리 구조의 객체들로 표현하는 형식을 말한다. 웹 브라우저가 사용할 스크립트 언어(JavaScript 등)와는 독립적으로, 일관된 방식으로 구조화된 문서를 여러 웹 브라우저들이 표현할 수 있도록 W3C가 정의한 표준이다. DOM 표준을 따르는 웹 브라우저는 HTML 등의 구조화된 문서의 내용을 내부 메모리에 트리 구조의 DOM 객체들을 생성함으로써 표현한다. 이를 통해, 그 위에서 동작하는 JavaScript 등의 스크립트 언어가 문서의 내용을 쉽게 제어하고 조작할 수 있게 되는 것이다.

 

👉️ DOM 트리? DOM API?

조금 더 구체적으로 표현하자면, DOM 트리를 이루는 DOM 객체라는 것은 사실상 Node 타입의 객체를 의미한다. 그리고 Node 타입의 대표적인 하위 타입으로는 Document 타입, Element 타입, CharacterData 타입이 있다. 즉 트리를 이루는 각각의 노드는 Doucment, Element, CharacterData 중 하나의 타입을 가지는 객체라고 보면 된다. DOM 트리의 엔트리 포인트에 해당하는 window.document 객체가 바로 Doucment 타입이며, <> 형태로 표현되는 각 태그는 Element 타입의 객체로 표현되고, 태그가 아닌 단순 텍스트 노드는 CharacterData 타입의 객체로 표현된다. 그리고 이러한 타입의 DOM 객체들로부터 제공되는 API가 곧 DOM API인 것이다. 예를 들어 Document 타입의 객체와 Element 타입의 객체가 제공하는 querySelector() 메소드와 Document 타입의 객체가 제공하는 getElementById() 메소드가 대표적인 DOM API이다. 참고로 Element 타입은 다시 HTMLElement 타입과 SVGElement 타입으로 나뉘는데, 일반적으로 HTML 문서에서 다루는 태그들은 HTMLElement 타입의 객체라고 생각하면 된다. 다소 지나치게 상세한 내용일 수 있는데, TypeScript 언어를 공부하는 사람이라면 DOM API를 다룰 때 이러한 타입 관련 지식들을 알고 있는 것이 좋다고 생각해서 여기서 한 번 소개해봤다.

 

2. BOM (Browser Object Model)

DOM이 웹 브라우저가 열고 있는 각 웹 페이지(문서)의 내용을 객체들로 표현하는 형식이라면, BOM은 해당 페이지를 열고 있는 웹 브라우저 자체를 객체들로 표현하는 형식을 말한다. 즉, 웹 브라우저는 실행되는 순간부터 내부 메모리에 웹 브라우저 자체에 대한 여러 정보들을 담고 있는 BOM 객체들을 생성함으로써 자기 자신(웹 브라우저)을 표현한다. 이를 통해, 그 위에서 동작하는 JavaScript 등의 스크립트 언어가 웹 브라우저를 쉽게 제어하고 조작할 수 있게 되는 것이다. 예를 들어, 뒤로 가기 기능 혹은 URL 이동 기능이 대표적인 사례이다.

 

👉️ BOM API? BOM 표준?

DOM API와 마찬가지로, BOM 객체들로부터 제공되는 API를 BOM API라고 생각하면 된다. 참고로 최상위 BOM 객체에 해당하는 window 객체는 사실 웹 브라우저 탭 하나 당 하나 존재하기 때문에, BOM API는 사실상 웹 브라우저 전체가 아닌 웹 브라우저 탭 하나를 기준으로 하고 있다. 예를 들어 뒤로 가기 기능의 경우 현재 탭에서만 동작하지 다른 탭에까지 영향을 미치지 않는다(물론 웹 브라우저 전체에 영향을 미치는 기능도 몇 개 있긴 함). 그리고 추가적으로 하나 더 기억해야 할 사실은, DOM과 달리 BOM은 공식적인 표준이 없기에 웹 브라우저마다 BOM API가 조금씩 다를 수 있다는 점이다. 그러나 대부분의 브라우저가 비슷한 BOM API를 제공하고 있긴 하다.

 

3. JavaScript 객체 구조

결론적으로, 웹 브라우저에서 JavaScript가 동작할 때 웹 브라우저의 내부 메모리에 생성되어 있는 웹 브라우저/페이지 관련 객체들의 구조는 다음과 같다. 모든 객체들의 최상단에는 전역 객체 window가 존재한다. BOM 객체들은 window 객체의 프로퍼티로서 존재하며, window 객체의 document 프로퍼티는 DOM 객체들이 이루는 트리의 최상단에 위치한다. window 객체는 모든 객체들의 최상단에 위치하기 때문에, window 객체의 프로퍼티나 메소드는 앞에 window를 생략하고 바로 사용할 수 있다. 대표적인 사례가 바로 alert("메시지")이다.

 

 

4. Web API (참고)

Web API는 웹 브라우저가 제공하는 기능을 의미하는 것으로, 앞서 언급한 DOM API나 BOM API가 Web API의 일부라고 생각하면 된다. 그런데 ECMAScript에서 정의하는 본질적인 JavaScript에서는 Web API, 즉 웹 브라우저가 제공하는 기능들까지 정의하고 있지 않다. 즉 Web API는 웹 브라우저가 제공하는 기능일 뿐, JavaScript 자체의 명세에는 포함되어 있지 않다는 것이다. 다만 각 웹 브라우저가 내장하고 있는 JavaScript 엔진이 ECMAScript에서 정의하는 JavaScript 자체의 명세뿐 아니라 Web API도 이해할 수 있도록 확장성 있게 구현이 되어 있을 뿐이다. 그래서 Node.js의 JavaScript 엔진이 크롬의 V8 엔진을 기반으로 한다는 말의 의미는, 크롬의 V8 엔진이 ECMAScript에서 정의하는 JavaScript 자체의 명세를 구현한 부분을 그대로 가져와서 엔진을 구축했다는 말이 된다. 다만 Node.js 엔진도 console.log() 함수와 같은 몇몇 Web API들은 이해할 수 있도록 어느 정도 확장성 있게 구축돼 있긴 하다.

 

5. 이벤트 캡쳐링, 이벤트 버블링 (Event Capturing/Bubbling)

이벤트의 발생(Dispatch)은 DOM 이벤트 객체의 생성을 의미하며, 이벤트의 처리(Handling)는 생성된 DOM 이벤트 객체를 감지하여 특정 동작을 취하도록 하는 것을 의미한다. 그렇다면 DOM 이벤트 객체는 어디서 생성되어 어떻게 감지되는 것일까? 이와 관련된 내용이 바로 이벤트 캡쳐링(Event Capturing)과 이벤트 버블링(Event Bubbling)이다.

 

특정 이벤트가 발생하면, 웹 브라우저는 해당 이벤트의 정보를 담고 있는 DOM 이벤트 객체를 생성한다. 그리고 이는 최상단에 위치한 window 객체에서부터 시작하여 이벤트가 발생한 DOM 객체를 향해 아래로 내려간다. 이렇게 이벤트가 아래로(부모에서 자식 방향으로) 전파되는 것을 이벤트 캡쳐링(Event Capturing)이라고 한다. 그리고 해당 이벤트가 최하단 DOM 객체에 도착하면 이제 다시 window 객체를 향해 위로 올라간다. 이렇게 이벤트가 위로(자식에서 부모 방향으로) 전파되는 것을 이벤트 버블링(Event Bubbling)이라고 한다.

 

▲ W3C에서 명시한 DOM 이벤트 객체의 전파 흐름

 

기본적으로 이벤트 핸들러는 버블링 과정에 있는 이벤트 객체를 감지하도록 되어 있다. 그러나 캡쳐링 과정에 있는 이벤트 객체를 감지하도록 변경하고 싶다면, 다음과 같이 이벤트 핸들러를 등록할 때 인자로 넘어가는 capture 옵션을 true로 설정해주면 된다.

const clickBtn = document.querySelector('#click_btn');
clickBtn.addEventListener('click', click_handler, true);

 

참고로 DOM 이벤트 객체의 target 프로퍼티는 이벤트가 발생한 위치의 최하단 DOM 객체를 의미한다. 반면에 currentTarget 프로퍼티는 현재 DOM 이벤트 객체가 처리되고 있는 위치의 DOM 객체(= 이벤트 핸들러가 부착된 요소)를 의미한다. 만약 이러한 이벤트의 전파(캡쳐링 혹은 버블링)를 차단하고 싶다면, 현재 처리하고 있는 DOM 이벤트 객체를 대상으로 stopPropagation 메소드를 호출하면 된다. 또한 jQuery의 경우, 이벤트가 발생할 때 생성된 DOM 이벤트 객체를 W3C에서 정의한 표준에 맞게 정규화하여 새로운 이벤트 객체를 생성한다. 이러한 정규화의 목적은 여러 웹 브라우저에서 용이하게 호환되도록 하기 위함인데, 본래의 DOM 이벤트 객체보다는 적은 정보를 가지고 있다. 따라서 본래의 DOM 이벤트 객체에 접근하고 싶다면 jQuery가 생성한 이벤트 객체의 originalEvent 프로퍼티를 사용하면 된다.