브라우저 동작 과정 - 02. Html 파싱과 Dom
🖐🏻 들어가며
간단하게 브라우저에 대해 공부를 해봤으니, 이제 스터디의 주 목적이었던 브라우저 렌더링 과정에 대해 단계별로 자세하게 알아보려고 한다. 먼저 렌더링 과정에서 가장 먼저 이루어지는 HTML 파싱과 DOM 트리 구축에 대한 내용이다.
🗂️ DOM, Document Object Model
DOM 은 문서 객체 모델 (Document Object Model)의 줄임말로, HTML 을 브라우저가 이해할 수 있는 node 타입의 Obejct 로 변환한 것이다. DOM 은 마크업과 1:1 관계를 맺게 되는데, 자세하게 들여다보기 전에 우선 아래 예제를 보고 간단하게 DOM 트리로 변환에 대해 알아보자.
✏️ DOM 트리 변환 예제
<html>
<body>
<p>리윤트와 DOM</p>
<div><img src="poordobby.png" /></div>
</body>
</html>
위 태그들이 DOM 트리로 변환되는 과정을 살펴보면,
서버는 브라우저로 부터 요청 받은 HTML 파일을 읽어들여 메모리에 저장을 하고,
메모리에 저장된 바이트
(10110100001000...)
을 응답한다.브라우저는 응답받은 바이트 형태의 문서를
meta
태그의charset
어트리뷰트에 지정된 인코딩 방식(UTF-8)에 따라 문자열로 반환한다. ( *코드에서는 해당 태그를 생략했다.)문자열로 반환이된 HTML 문서는 문법적 의미를 갖는 코드의 최소 단위인
토큰(token)
으로 분해된다.토큰들의 내용에 따라 객체로 변환하고 문서/요소/어트리뷰트/텍스트 노드 등 각각의 노드들을 생성한다.
HTML 은 요소 간의 부자 관계인 중첩 관계를 가지며, 이를 반영해 모든 노드들을 트리 구조로 구성하고 DOM을 형성한다.
✏️ 노드와 트리
🍃 노드 Node
노드는 컴퓨터 네트워크에서 네트워크에 연결된 한 개의 기계를 의미하는데, 상황에 따라 다르게 정의될 수 있다. DOM 구조에서의 노드는 하나의 ‘점’이라고 볼 수 있다. 예를 들어 Root Element를 포함한 각각의 element 들은 모두 노드라고 볼 수 있다.
🌲 트리 Tree
DOM 구조를 보면 위에서 아래로 점점 퍼져나가는 구조를 볼 수 있는데, 이러한 구조의 그래프를 ‘트리 구조’라고 부른다. 트리 구조에서 상단에 위치하는 노드는 부모 노드 (Parent Node)라고 하며, 하위에 존재하는 노드는 자식 노드 (Child Node)라고 부른다.
또한 최상단에 위치하는 부모 노드가 없는 노드는 루트 노드 (Root Node)라고 하며, 반대로 자식 노드가 없는 최하위 노드는 리프 노드(Leaf Node)라고 불린다. 루트 노드를 제외한 모든 노드는 하나의 부모 노드만 가질 수 있으며, 모든 요소의 노드들은 자식 노드를 가질 수 있다.
하나의 부모 노드는 여러개의 자식 노드를 가질 수 있는데, 이 때 부모 노드가 같은 노드는 형제 노드(Sibling Node)가 된다. 부모 노드를 포함해 현재 위치보다 상위에 위치한 모든 노드는 조상 노드(Ancestor Node), 반대의 케이스는 자손 노드(Descendant Node)라 불린다.
✏️ DOM 트리
DOM 트리 내 하나의 객체는 노드라 부르며, DOM 트리는 총 4가지 노드로 구성되어 있다.
문서 노드 Document Node : 트리의 최상위 객체로 DOM 트리에 접근하기 위한 시작점
요소 노드 Element Node : HTML 요소 (태그) 를 객체로 표현한 것
- Element :
<p> ... </p>
- Element :
어트리뷰트 노트 Attribute Node : HTML 요소의
Attribute
를 객체로 표현한 것- Attributes :
id = "main"
- Attributes :
텍스트 노드 Text Node : HTML 요소의 “텍스트”를 객체로 표현한 것
- Text nodes :
<em>리윤트</em>
- Text nodes :
✏️ DOM 생성 방법
HTML과 마찬가지로 DOM은 W3C에 의해 명세가 정해져 있고, 이것은 문서를 다루기 위한 일반적인 명세로 부분적으로 HTML 요소를 설명하기도 한다. HTML 정의는 이 링크에서 확인할 수 있다.
DOM 이 생성되는 방법을 간단하게 위 예제를 통해 봤는데, DOM이 생성되는 방식은 위와 예시 코드와 같은 정적인 과정도 있지만 동적과정으로도 생성이 된다.
HTML 태그가 작성된 문서를 읽어 문서 객체를 생성하는 방법 → 정적 과정
원래 존재하던 HTML 페이지에 javascript 를 이용해 문서 객체를 생성하는 방법 → 동적과정
HTML 태그로 작성된 문서를 자바스크립트를 이용해 수정하게 된다면, HTML의 각 요소(DOM 요소)에 접근해서 수정하게 된다.
👩🏻💻 파싱 알고리즘
앞선 파트에서 DOM 트리 형성 과정에서 문자, 토큰, 노드에 대해서 간단히 언급을 했었는데 이 부분을 조금 더 자세히 들여다보자.
1️⃣ 파싱이란?
먼저, 파싱(Parsing)이란 하나의 프로그램을 런타임 환경(브라우저의 자바스크립트 엔진)이 실제로 행할 수 있는 내부 포맷으로 분석하고 변환하는 것을 의미한다. 문서 파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미하는데 즉, 파싱은 문서의 내용을 토큰으로 분석하고 문법적 의미와 구조를 반영한 파스 트리(parse tree) 또는 문법 트리(syntax tree)를 생성하는 과정이다. 이에 따라, HTML 의 파싱 알고리즘은 토큰화
와 트리 구축
의 두 단계로 구성이 된다.
파서 - 어휘 분석기 조합
파싱은 어휘 분석과 구문 분석이라는 두 가지로 구분할 수 있다. 어휘 분석은 자료를 토큰으로 분해하는 과정이다. 토큰은 유효하게 구성된 단위의 집합체로 용어집이라고도 할 수 있는데, 인간의 언어로 말하자면 사전에 등장하는 모든 단어에 해당된다. 구문 분석은 언어의 구문 규칙을 적용하는 과정이다.
파서는 앞서 언급한 것 처럼 보통 두 가지 일을 한다. 자료를 유효한 토큰으로 분해하는 어휘 분석기(토큰 변환기)가 있고 언어 구문 규칙에 따라 문서 구조를 분석함으로써 파싱 트리를 생성하는 파서가 있다. 어휘 분석기는 공백과 줄 바꿈 같은 의미없는 문자를 제거한다.
파싱 과정은 반복되며 파서는 보통 어휘분석기로부터 새 토큰을 받아 구문 규칙과 일치하는지 확인한다. 규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청한다. 규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때 까지 요청한다. 맞는 규칙이 없는 경우에는 예외로 처리하는데, 이것은 문서가 유효하지 않고 구문 오류를 포함하고 있다는 의미이다.
변환
파서 트리는 최종 결과물은 아니다. 파싱은 보통 문서를 다른 양식으로 변환하는데 컴파일이 하나의 예가 된다. 소스 코드를 기계코드로 만드는 “컴파일러”는 파싱 트리 생성 후 이를 기계 코드 문서로 변환한다.
2️⃣ 토큰화란?
토큰화란 어휘 분석으로서 입력 값을 토큰으로 파싱하며, HTML 에서 토큰은 시작 태그, 종료 태그, 속성 이름과 속성 값을 의미한다. 토큰화는 토큰을 인지해서 트리 생성자로 넘기고 다음 토큰을 확인하기 위해 다음 문자를 확인한다. 그리고 입력의 마지막까지 이 과정을 반복한다.
3️⃣ 토큰화 알고리즘
알고리즘의 결과물은 HTML 토큰이 된다. 알고리즘은 상태 기계(State Machine)이라고 볼 수 있는데, 각 상태는 하나 이상의 연속된 문자를 입력을 받아서 이 문자에 따라 다음 상태를 갱신한다. 그 결과는 현재의 토큰화 상태와 트리 구축 상태의 영향을 받는데, 이것은 같은 문자를 읽었다고 하더라도 현재 상태에 따라 다음 상태의 결과가 다르게 나온다는 것을 의미한다.
<html>
<body>
리윤트
</body>
</html>
초기 상태는 “자료 상태”이다.
<
문자를 만나면 “태그 열림 상태”로 변한다.a-z
의 문자를 만나면시작 태그 토큰
을 생성하고 상태는태그 이름 상태
로 변하는데, 이 상태는>
문자를 만날 때까지 유지된다.각 문자에는 새로운 토큰 이름이 붙게 되는데 이 경우 생성된 토큰은
html 토큰
이다.>
문자에 도달하면 현재 토큰이 발행되고 상태는 다시 “자료 상태”로 변환된다.태그는 동일한 절차에 따라 처리되며 그렇게
html
태그와body
태그를 발행해 “자료 상태”로 돌아온다.“리윤트”의 “ㄹ” 문자를 만나면
문자 토큰
이 생성되고 발행될 것이다.이것은 종료 태그의
<
문자를 만날 때까지 진행되여 “리윤트”의 각 문자를 위한 문자 토큰을 발행한다.다시
<
문자를 만나면 “태그 열림 상태”로 벼경되며,/
문자는종료 태그 토큰
을 생성하고“태그 이름 상태”로 변경된다. 이 상태는
>
문자를 만날 때까지 유지된다.그리고 새로운
태그 토큰
이 발행되고 다시 “자료 상태”가 된다. 이후로는 또 동일하게 처리가 된다.
4️⃣ 트리 구축 알고리즘
파서가 생성이 되면 문서 객체가 생성된다. 트리 구축이 진행되는 동안 문서 최상단에서는 DOM 트리가 수정되고 요소가 추가된다. 토큰화에 의해 발행된 각 노드는 트리 생성자에 의해 처리된다. 각 토큰을 위한 DOM 요소의 명세는 정의되어 있다. DOM 트리에서 요소를 추가하는 것이 아니라면 열린 요소는 스택(임시 버퍼 저장소)에 추가된다. 이 스택은 부정확한 중첩과 종료되지 않은 태그를 교정한다. 알고리즘은 상태 기계라고 설명할 수 있고 상태는 “삽입 모드”라고 부른다.
- 트리 구축 알고리즘 예제
<html>
<body>
리윤트
</body>
</html>
트리 구축 단계의 입력 값은 토큰화 단계에서 만들어지는 일련의 토큰이다.
받은 html 토큰은
html 이전
모드가 되고 토큰은 이 모드에서 처리된다.이것은
HTMLHtmlElement
요소를 생성하고 문서 객체의 최상단에 추가된다.상태는
"head 이전"
모드로 바뀌었고,"body"
토큰을 받았다."head"
토큰이 없더라도HTMLHeadElement
는 묵시적으로 생성되어 트리에 추가될 것이다.곧이어
"head 내부"
모드로 이동했고 다음은"head 다음"
모드로 간다.body
토큰이 처리되었고HTMLBodyElement
가 생성되어 추가되며"body 안쪽"
모드로 전환된다."리윤트"
문자열의 문자 토큰을 받았다.첫 번째 토큰이 생성되고
"본문"
노드가 추가되면서 다른 문자들이 그 노드에 추가될 것이다.body
종료 토큰을 받으면"body 다음"
모드가 된다.html 종료 태그
를 만나면body 다음 다음
모드로 바뀐다.마지막 파일 토큰을 받으면 파싱이 종료된다.
❌ 브라우저의 오류 처리
HTML 페이지에서 “유효하지 않은 구문”이라는 오류를 본적은 없었다. 이는 브라우저가 모든 오류 구문을 교정하기 때문인데, 아래 예제를 확인해보자.
<html>
<errortag></errortag>
<div><p></div>이게 되네?</p>
</html>
위 코드들은 여러 규칙이 위반된 상태이다. <errortag/>
는 표준 태그가 아니며, <p>
태그와 <div>
태그는 중첩오류가 있다. 하지만 브라우저는 여기서 오류를 내뱉지 않고 올바르게 표시가 되는데, 이는 파서가 HTML 을 작성한 개발자의 실수를 자동으로 수정을 했기 때문이다. 상단 우측 스크린샷을 보면 정말 오류 없이 실행되고 있음을 확인할 수 있다.
파서는 토큰화된 입력 값을 파싱해서 문서를 만들고 문서 트리를 생성한다. 규칙에 맞게 잘 작성된 문서라면 파싱이 수월하지만 형식에 맞지않게 작성된 HTML 문서를 다룰 때는 여러가지 오류를 처리해야한다.
파서는 이 과정에서 다음과 같은 오류를 처리해야하는데,
어떤 태그의 내부에 추가하려는 태그가 금지된 것일 때 허용된 태그 먼저 닫고 금지된 태그는 외부에 추가한다.
파서가 직접 요소를 추가해서는 안된다. 문서 제작자에 의해 뒤늦게 요소가 추가될 수 있고 생략 가능한 경우도 있다.
HTML
,HEAD
,BODY
,TBODY
,TR
,TD
,LI
태그가 이런 경우에 해당한다.인라인 요소 안쪽에 블록 요소가 있는 경우는 부모 블록 요소를 만날 때 까지 모든 인라인 태그를 닫는다.
이런 방법이 도움이 안될 때는 태그를 추가하거나 무시가능한 상태가 될 때까지 요소를 닫는다.
👩🏻💻 마무리
HTML 태그들이 어떤식으로 파싱이 이루어지고 어떻게 DOM 트리를 생성하는지에 대해 알아봤다. 내가 작성하고 있는 코드들이 이루는 거대한 DOM 을 지금까지는 만들어진 결과물을 두고만 바라봤었는데, 생성이 되는 원리부터 다가가니 지금껏 생각없이 쓰고 있던 것들에 대해서도 이해가 가는 부분들도 많았던 것 같다. 다음엔 CSSOM 에 대해 알아보자!
💾 참고
Subscribe to my newsletter
Read articles from leetrue directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
leetrue
leetrue
직면하는 모든 문제에 유치한 것은 없으며, 의미 없는 삽질 또한 없다고 믿습니다.