일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 프로그래머스
- 블로그 서비스 최적화
- Babel과 Webpack
- 스코프
- 디스트럭처링
- 전역변수의문제점
- 빌트인 객체
- var 사용금지
- 프로퍼티 어트리뷰트
- 이미지 갤러리 최적화
- package management
- 프론트엔드 성능 최적화 가이드
- 비전공이지만 개발자로 먹고삽니다
- 커리어
- Property Attribute
- 올림픽 통계 서비스 최적화
- 이벤트
- 자바스크립트 딥다이브
- 딥다이브
- 자바스크립트
- const
- ES6함수 추가기능
- 모던 자바스크립트 Deep Dive
- DOM
- peerdependencies
- 자바스크립트 패턴
- 제너레이터와 async/await
- 인터넷 장비
- 브라우저의 렌더링 과정
- Set과 Map
- Today
- Total
Dev Blog
DOM/CSSOM/BOM/Virtual DOM 본문
Browser Rendering: JS + DOM + CSSOM
There's a thing called the Critical Rendering Path (CRP), which is the pipeline for rendering a page. In other words, it's the steps the browser needs to take to create pixels into the screen from your HTML, CSS, and JavaScript code. Now, CSS and HTML are so-called render-blocking resources, meaning, they need to be fetched and fully parsed before we can render our page. The parsing of HTML and CSS creates two trees, the DOM and the CSSOM. Furthermore, during this process, JavaScript can be both loaded and executed. The time it takes to parse, fetch and execute things is known as blocking, which subsequently slows down the page load time.
Table of Contents:
- Document Object Model (DOM)
- Browser Object Model(BOM)
- Cascading Style Sheets Object Model (CSSOM)
- Loading and Executing JavaScript
- Critical Rendering Path (CRP)
- Virtual DOM
Document Object Model(DOM)
The Document Object Model (DOM) is the result of parsing HTML-code. When we write HTML documents we encapsulate HTML within HTML, this creates a hierarchy that we can use to create a tree. The browser parses that hierarchy (the document’s structure) into a tree of nodes. We may refer to the result as a syntax tree or parse tree. To be clear, we parse our HTML-code into a tree of *node objects that represents the DOM, this is done since the DOM itself is an interface used for scripting. It's the browser that deals with the creation of the nodes for the initial loading of the HTML document.
The Document Object Model (DOM) is a cross-platform and language-independent convention for representing and interacting with objects in HTML, XHTML, and XML documents. The nodes of every document are organized in a tree structure, called the DOM tree. Objects in the DOM tree may be addressed and manipulated by using methods on the objects. The public interface of a DOM is specified in its application programming interface (API).
What is Node?
All node objects inherit the Node interface. This interface has the essential properties and methods for manipulating, inspecting, and traversing the DOM. So, there are different kinds of nodes, examples are Document, Element, CharacterData, DocumentFragment. They all inherit from Node, but they also have their unique attributes. You can use nodeType and nodeName when you want to know the available properties on a node:
// <p>hello</p>
const p = document.getElementsByTagName('p')[0];
p.nodeType; // 1 === Node.ELEMENT_NODE
p.nodeName; // P
1. Document Node
The DOM is itself a node object, a Document node. The Document node can hold two different types of nodes. The first being, the DocumentType node object which represents a DocType (<!DOCTYPE>). The DocType declares the markup language and what version of it that the document uses. The second node that the Document node holds is an ElementType node object.
<!DOCTYPE html> <html></html>
We can get the references to these nodes easily via document, like document.doctype, document.documentElement, document.body and so on. Lastly, even though the browser parses our HTML on the initial load, we can still create nodes after with document.createElement(tag: string).
2. Element Node
There are many different types of Element nodes like html, body, span, h1, and the list goes on. These elements can have attributes, for example with <div id="1" class="my-class"></div>, we have two attributes which are id and class. The attributes themselves are Attr nodes, we can access the attributes by using the .attributes property. If you want to get, set or remove an attribute you can use the following:
- Element.getAttribute(attrName: string)
- Element.setAttribute(attrName: string, attrValue: string)
- Element.removeAttribute(attrName: string)
- Element.hasAttribute(attrName: string)
- Or just use the .attributes property directly.
Furthermore you've probably sometime used more than one class for an element, like <span class="class1 class2"></span>. When working with the class attribute you can use Element.classList to get the classes in an object like { length: 2, value: "class1 class2", 0: "class1", 1: "class2" }, or simply do Element.className to get "class1 class2". You also have the following add and remove methods when working with classes:
- Element.classList.add(className: string)
- Element.classList.remove(className: string)
- Element.classList.toggle(className: string)
- Element.classList.contains(className: string)
3. Text Node
The text scattered around in your HTML-code will be parsed into Text node objects. This also applies to whitespaces, since it's a character. So <div> </div> would, for example, have a child node that's a text node. Furthermore, when you use Element.textContent it'll concatenate all the text nodes within that element (including the children's text nodes, etc) and return it as a string. You can also use Element.textContent to set a new single text node while also removing all other text nodes.
On a side note, innerText is similar to textContent, but it ignores the text if it's hidden by CSS or inside script or style tags, while textContent does not.
3-1. How to create, insert, replace, remove and clone Nodes
The properties/methods innerHTML, outerHTML, textContent, and insertAdjacentHTML() allows us to use strings when adding elements to the DOM. However, a word of caution when doing so is that some of these methods invoke an expensive HTML parsing process. The following code would remove all the content in your body tag and then add some content:
document.body.innerHTML = '';
document.body.innerHTML += 'add1'; // add1
document.body.innerHTML += 'add2'; // add1add2
We also have the appendChild() and insertBefore() methods that we can use to add a node object as a child to another node object. In the example below, we create an h1 object, adding text to it and then appending it to the body tag:
let h1 = document.createElement('h1');
h1.innerText = 'hi all';
document.body.appendChild(h1);
Then we have the methods, removeChild() and replaceChild() which both do just as their name implies. The methods return the reference to the node object, so the node gets removed from its parent, not memory. These methods can be somewhat tricky to invoke since we pass the reference of the node object to the methods, but the methods are invoked on the parent, like parentOfNodeObj.removeChild(referenceToNodeObj).
…
<body>
<h1>hello</h1>
<p>world</p>
<script>
let h1 = document.body.firstChild;
h1.parentNode.removeChild(h1); // same as document.body.removeChild(h1)
</script>
...
To clone a node you simply do let newNode = oldNode.clone(). If you also want to clone its children you must pass true, like let newNode = oldNode.clone(true). A word of caution, since the attributes and values are copied you might encounter duplicates of element IDs in a document.
3-2. Selecting and Traversing Element Nodes
When we want to get a single element we can use getElementById or querySelector(). The querySelector() has the following characteristics:
- Returns the first node that’s found.
- You pass it a CSS3 selector.
- Can be used like Element.querySelector(), which means it only searches that particular part of the DOM tree.
As you see, they return the first found node. So, how do we select multiple nodes? Well, the following methods can help us out:
- querySelectorAll(CSSselector: string)
- getElementsByTagName(tag: string)
- getElementsByClassName(className: string)
These methods create a list containing the elements. But be aware that these can cause unexpected behaviours, querySelectorAll() returns a snapshot of the current state, while the other two returns lists that always represent the current state of the DOM. Study the following code:
// Creating and setting up our Element node
let div = document.createElement('div');
div.setAttribute('class', 'classValue');
div.appendChild(document.createTextNode('hello'));
// Adding the div to the body
document.body.appendChild(div);
// Getting the div in two lists
let queryList = document.querySelectorAll('.classValue');
let classList = document.getElementsByClassName('classValue');
// We get a list back containing our div
console.log(queryList); // NodeList [div.classValue]
console.log(classList); // HTMLCollection [div.classValue]
// Remove the div from the DOM
document.body.removeChild(div);
// As we see queryList contains a snapshot while classList the current state
console.log(queryList); // NodeList [div.classValue]
console.log(classList); // HTMLCollection []
4. NodeList vs HTMLCollection
When trying to get multiple elements, you'll likely encounter NodeList and HTMLCollection. They are both read-only array-like objects, in other words, both are collections of DOM nodes. They are similar but differ slightly, for example, HTMLCollection only contains elements while NodeList may contain other nodes than element, but rarely does. It's important to know that a NodeList can both be live and static. If it's live it will update according to the DOM's state, if it's static it'll be a snapshot of the current state of the DOM.
5. Traversing the DOM with Node properties
Properties like childNodes, firstChild, and nextSibling gives a way to traverse (travel around in) the DOM tree. A word of caution, traversing with these properties will include text and comment nodes (non-element nodes) which can cause unexpected behaviours. You can avoid traversing text and comment nodes by using firstElementChild, lastElementChild, nextElementChild, previousElementChild, childElementCount, children, and parentElement.
BOM(Browser Object Model)
We also have the Browser Object Model (BOM), which allows our JavaScript code to interact with the browser. The BOM is a collection of browser objects. The top-level object in the BOM is the window object, which represents an open browser window. Examples of browser objects are navigator, screen, history and also the DOM (document), they are all accessible via the window object. Unlike the DOM, the BOM does not have a standard for implementation and is not strictly defined, which subsequently means that the BOM varies depending on which browser you use. Furthermore, the window object is where all globals are put into.
The BOM (Browser Object Model) consists of the objects navigator, history, screen, location and document w
hich are children of window.
location 객체 사용예시
alert(location.href); // 현재 URL을 보여줌
if (confirm("위키피디아 페이지로 가시겠습니까?")) {
location.href = "https://wikipedia.org"; // 새로운 페이지로 넘어감
}
CSSOM(CSS Object Model)
CSS stands for Cascading Style Sheets and is a language that describes the visual representation of our HTML elements. The CSS Object Model is a set of APIs allowing the manipulation of CSS from JavaScript. It is much like the DOM, but for the CSS rather than the HTML. It allows users to read and modify CSS style dynamically.
The values of CSS are represented untyped, that is using String objects.
Now, the process of both fetching and parsing the CSS is render-blocking because the CSSOM is needed to create a render tree (more on this later). To decide which CSS to parse, we can use three different ways, inline, internal/embedded, or external.
1. Inline
Inline styling entails passing a string containing the CSS to the attribute style on HTML elements:
<div style="color: green;"></div>
2. Internal/embedded
Internal stylesheets are embedded in a <head> tag with <style> (HTMLStyleElement):
<html>
<head>
<style>
… style here …
</style>
</head>
</html>
3. External
External stylesheets are files containing CSS that usually have a.css extension. Externals require downloading, which increases the render-blocking aspect. To include external files in our document, we use the HTMLLinkElement like:
<html>
<head>
<link href="/style.css" rel="stylesheet" type="text/css">
</head>
</html>
Interacting with CSSOM
CSSOM also gives us APIs to find out things like the size and position of our elements. It's possible to interact with the CSSOM via JavaScript by accessing the style property on HTML elements. But the style property only contains the inline CSS that's defined via the element's style attribute. Luckily, you can use window.getComputedStyle(el: element) to get a CSSStylesheet object that contains both the inline CSS and the CSS from the cascade. TheCSSStylesheet object itself contains CSSStyleRule objects that you can manipulate with CSSStylesheet.insertRule() and CSSStylesheet.deleteRule() but that's very uncommon. Lastly, since each stylesheet corresponds to a CSSStylesheet object you can disable and enable them by toggling their disabled boolean property.
// get specific
element.style.height;
// set
element.style.height = '100px';
Loading and Executing JavaScript
JavaScript lets us manipulate both the DOM and CSSOM. JavaScript is parsing-blocking, which means that when the parser encounters a <script> tag, it'll stop the construction of the DOM. The following will happen:
- Stop the construction of DOM.
- Fetch the JS code if external.
- Construct the CSSOM if not constructed (CSS is script blocking).
- Execute the JS code.
- Resume the construction of DOM.
As you see JavaScript can block parsing, so we must carefully consider where we put our script tags. We can add JavaScript in three ways, external, element inline, and page inline. Now, on a side note, it might seem strange that the CSSOM must be created before JavaScript can be executed. But this is because we might try to access style that has not been defined yet, like document.body.style.
Critical Rendering Path (CRP)
The Critical Rendering Path is, in essence, all the steps required by the browser to create pixels into the screen. So far, we’ve covered the DOM, CSSOM, and how JavaScript is loaded and executed. They are all part of the Critical Rendering Path. The image below shows the pipeline for rendering a page:
As we see the browser uses the constructed CSSOM and DOM to create a render tree. The render tree only contains the visible nodes, which means we exclude script tags, meta tags, elements whose style makes them invisible, etc. Once a render tree is created, the layout is computed, followed by the actual paint. Our goal should be to make this process as smooth and quick as possible. This is why we need to be aware of render-blocking resources.
Closing remarks
We've learned that it matters how we structure our HTML elements. A common phrase is, "put the CSS at the top and the script at the bottom". After looking at how the browser renders our HTML, CSS, and JavaScript, we can put them into three main layers:
- Structure (HTML)
- Presentation (CSS)
- Behaviour (JavaScript + DOM + CSSOM)
We've looked at how the CRP correlates to a quick first paint (FP). But CRP also correlates to achieving high FPS, which might seem strange because we are creating websites, not "real games". But having low FPS causes page jank, which leads to bad user experience. Nevertheless, now you should have a good foundation on browser rendering and how to work with the DOM.
Virtual DOM?
React, Vue.js 및 Elm과 같은 선언적 웹 프레임 워크에서 사용되는 DOM의 경량 JavaScript 표현이다.
가상 돔은 그 과정을 브라우저 단에서 효율적으로 해내기 위해 만들어졌는데, 사용자가 작성한 React 전용 코드를 브라우저가 가상 돔으로 해석해서 메모리에서 먼저 구현한 다음 최종적으로 실제 DOM 에 적용하는 것이다.
React 등이 거치는 과정은 사이트가 실행되는 시점인 런타임에서 일어나는데 즉, 브라우저가 자바스크립트 파일을 받아서 하는 작업이다.
Runtime 이라는 제약조건 하에서 브라우저에 로드된 라이브러리로서, DOM 에 가해지는 변화들을 최소한의 변경으로 구현할 방법을 찾는 방식이다.
Svelte 의 경우, 브라우저로 결과물을 전달하기 전에 컴퓨터에서 Node.js 로 컴파일하는 과정에서 이루어지기 때문에, 사이트에서 딱 실제로 일어날 일들만 컴팩트한 자바스크립트 파일로 컴파일 해 내놓는 것이다.
참고
브라우저 환경과 다양한 명세서: https://ko.javascript.info/browser-environment
Browser Rendering: JS + DOM + CSSOM: https://programmingsoup.com/browser-rendering
'Web Development > General Tech Knowledges' 카테고리의 다른 글
PWA (0) | 2021.08.01 |
---|---|
SPA - CSR/SSR/SSG (0) | 2021.08.01 |
배경이미지 브라우저에 꽉 채우기 (0) | 2021.05.21 |
How to improve an E-Commerce Checkout Experience: UI/UX Case study (0) | 2021.05.11 |
How to use Real-time DB of Firebase (0) | 2021.04.20 |