header

Day 8 - 2022/10/26

💡 새롭게 알게된 점

HTML과 CSS

DOM

DOM이란 Document Object Model의 약자로, 추상적인 개념의 HTML 문서의 태그도 모델링되어 객체로 만들어진 것

DOM의 각 요소는 key, value 형태로 이루어져 Web UI의 상태, 기능, 속성을 객체로 뽑아낸 것

DOM의 구조

트리 구조로 이루어짐

문서 노드

최상위 노드로, 모든 노드에 접근하기 위해서는 문서 노드를 통해야함

DOM Tree의 시작점

요소 노드

HTML 태그 그 자체를 나타냄

HTML 문서 구조를 그대로 나타냄

속성 노드나 텍스트 노드에 접근하기 위해 요소 노드를 찾아야함

속성 노드

요소 노드에 붙어있는 노드로, 자식 노드가 아님

태그에 정의되어 있는 속성들이 속성 노드에 속함

텍스트 노드

요소의 텍스트를 표현함

자식 노드를 가질 수 없기 때문에 DOM Tree의 단말 노드가 됨

DOM Tree에서의 순회

DOM Tree 순회는 PreOrder(전위순회)로 이루어짐

DOM Tree의 렌더링

먼저 브라우저는 HTML을 읽고 파싱한 후에 DOM Tree를 구성

이어서 Style Sheets를 파싱하여 Style Rules를 만들어 DOM 요소에 스타일을 입혀 만들어 냄

위 과정을 Attachment라고 함

그 후 Render Tree를 만듦. Layout 혹은 Reflow 과정을 통해 DOM 노드의 위치를 정해줌

Render Tree가 완성되면 만들어진 Tree를 통해 실제 화면에 그림

HTML을 읽고 파싱한 것을 DOM Tree라 하고, Style Sheets를 읽고 파싱한 것을 CSSOM Tree라 하는데, 이 두 Tree가 결합하여 Render Tree가 생성되고 브라우저는 이 Render Tree를 읽어 위치와 그래픽을 계산하여 화면에 출력

이 때 DOM이나 스타일이 수정될 경우, 이 Process를 다시 반복

많은 최적화를 통해 엄청 빠르지만, DOM을 한 번 조작할 때마다 다시 레이아웃을 잡고 렌더링해야하는 문제가 존재


DOM 조작해보기

간단한 에디터를 만들어보자!

먼저 html 파일을 만들어 toolbar와 editor로 쓸 div 태그를 생성한 다음 기본적인 css를 설정

// index.js

{...}
<body>
    <div class='toolbar'>
        <button>Button</button>
    </div>

    <div class='editor' contenteditable='true'></div>
</body> 
{...}

// index.css

.toolbar {
    width: 600px;
    height: 40px;
    padding: 8px;
    border-radius: 4px;
    background-color: bisque;
    box-sizing: border-box;
}

.toolbar button {
    height: 24px;
    border: 2px solid black;
    background-color: white;
    cursor: pointer;
}

.toolbar button:hover {
    background-color: gray;
}

.editor {
    width: 600px;
    height: 600px;
    padding: 16px;
    margin-top: 8px;
    font-size: 12px;
    border-radius: 4px;
    border: 2px solid black;
    outline: none;
    box-sizing: border-box;
}

조작할 버튼에 class를 설정해주고, js 파일을 생성해 즉시 실행 함수로 버튼에 이벤트를 등록

// index.html

{...}
<div class='toolbar'>
    <button class='bold'>Bold</button>
    <button class='italic'>Italic</button>
</div>
{...}

// index.js

// 즉시 실행 함수
(() => {
    document.querySelector('.bold').addEventListener('click', () => {
        document.execCommand('bold');
    });

    document.querySelector('.italic').addEventListener('click', () => {
        document.execCommand('italic');
    });
})()

새로운 버튼을 추가할 때마다 중복되는 부분이 많아지기에 tag 속 data-command를 통해 각 버튼의 속성을 추가하고, getAttribute로 속성 값을 받아와 속성 값에 따른 command를 이벤트로 등록

// index.html
{...}
<div class='toolbar'>
    <button data-command='bold'>Bold</button>
    <button data-command='italic'>Italic</button>
</div>
{...}

// 즉시 실행 함수
(() => {
    document.querySelectorAll('.toolbar button')
        .forEach(element => {
            element.addEventListener('click', (e) => {
                const command = e.target.getAttribute('data-command');
                document.execCommand(command);
            });
        });
})()

그 후 텍스트 에디터의 구색을 맞추기 위해 다른 버튼들도 추가해주면 세상에서 제일 간단한 텍스트 에디터 완성!


Virtual DOM

Virtual DOM(가상 돔)은 실제 DOM Tree를 자바스크립트 객체로 만든 것으로, 필요한 정보만 담아 만들어짐

이벤트나 DOM이 수정되는 한 틱 내에 직접 DOM을 수정하지 않고 Virtual DOM을 바뀌는 부분만 수정한 후 렌더링하면 브라우저 렌더링 프로세스가 줄어듦

이렇기 때문에 React.js 나 Vue.js에서도 Virtual DOM을 사용!

그럼 Virtual DOM이 DOM보다 빠른건가요?

Nope! React의 핵심 개발자가 말하기를, Virtual DOM이 DOM보다 빠르다는 것은 미신이고 유지보수에 용이한 애플리케이션을 만들도록 도와주고 대부분의 유스케이스에 충분히 빠를 뿐이라고 한다.

페이지에 그려진 DOM이 많을수록 느려지는 것은 똑같고, 탐색을 Virtual DOM과 DOM에서 두 번 해야하기 때문에 브라우저 렌더링 횟수를 줄여줄 뿐 실제로는 더 느리다. 심지어 메모리도 더 많이 사용하게 된다.

근데 왜 Virtual DOM을 사용하지?

일반적인 웹 사이트에서 그 정도로 성능이 소요되는 일은 거의 없고, 빠른 개발을 위해 성능 최적화보다 개발 편의성이 중요할 때가 더 많기 때문에 Virtual DOM을 많이 사용한다.

만약 정말 빠른 성능이 필요하다면..?

DocumentFragment를 사용하기?


Document.createDocumentFragment()

DocumentFragment는 부모가 없는 최소화된 경량화된 문서 객체이다.

DocumentFragment는 기본적으로 DOM과 동일하게 동작하지만, HTML의 DOM Tree에는 영향을 주지 않으며, 메모리에서만 정의된다.

DocumentFragment의 특징을 알아보자!

DocumentFragment는 사용시 자동으로 이전된다.

DocumentFragment 객체 생성 후 자식으로 다른 노드들을 추가 한 다음 DocumentFragment를 body에 자식으로 추가한다면, DocumentFragment에는 남아있는 내용이 없다. 즉, 빈 객체가 된다.

DocumentFragment를 자식으로 추가할 때 DocumentFragment는 추가되지 않는다.

위의 예에서 body에 추가되는 노드들은 DocumentFragment의 자식 노드들부터 추가된다.

그럼 DocumentFragment가 성능 최적화랑 무슨 연관이 있을까?

DocumentFragment는 DOM에 반영되기 전까지 메모리 상에서만 존재하기 때문에, DocumentFragment에 변경이 일어난다 하더라도 DOM의 구조는 변경되지 않아 브라우저가 화면을 다시 렌더링하지 않는다. 즉, Reflow나 Repaint가 일어나지 않는다.

다시 말해 Element을 이용해 수정한다면 수정 횟수만큼 렌더링을 다시 하고, DocumentFragment를 이용해 수정한다면 몇 번을 수정하더라도 화면에 반영을 한 번만 수행한다면 정확히 한 번은 아니지만 렌더링의 횟수가 그 만큼 줄어들기 때문에 성능 최적화를 이룰 수 있다.



❗️ 느낀점

여태까지 HTML, CSS에 대해서 공부할 때 렌더링 과정은 딱히 생각해보진 않았던 것 같다.

그래서 React 내부에서 최적화에 대해서는 고민해보았지만, DOM의 렌더링 성능 최적화에 대해서도 깊게 고민해보지 않았고, DocumentFragment도 난생 처음 들어보는 단어였다.

아주 기초적인 HTML, CSS에서도 모르는 내용이 많으니 나태해지지 말고 기초부터 공부하는 습관을 기르도록 하자!

오늘도 고생했다👊