
자바스크립트가 가장 많이 사용되는 분야는 웹 브라우저 환경에서 동작하는 웹 페이지/애플리케이션의 클라이언트 사이드입니다.
물론, 구글의 V8 자바스크립트 엔진으로 빌드된 자바스크립트 런타임 환경인 Node.js의 등장으로 자바스크립트 언어는 웹 브라우저를 벗어나 서버사이드 애플리케이션 개발에서도 사용할 수 있습니다.
대부분 프로그래밍 언어는 운영체제나 가상머신 위에서 실행되지만 웹 애플리케이션의 클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행됩니다.
오늘은 브라우저에서 어떻게 동작하는지를 알아봅시다!
렌더링은 HTML,CSS,JS로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말합니다.
브라우저 렌더링은 다음과 같은 과정으로 진행됩니다.
브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답 받음
브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성
브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행
이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있음
변경된 DOM과 CSSOM은 다시 렌더 트리로 결합
렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅
파싱..?
프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어들여 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고 토큰에 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스트리를 생성하는 일련의 과정
필요한 리소스를 서버에 요청하고 서버로부터 응답받아 브라우저에서 시각적으로 렌더링을 하게되는데요,
렌더링에 필요한 리소스는 모두 서버에 존재하므로 필요한 리소스를 서버에 요청하고 서버가 응답한 리소스를 파싱하여 렌더링한답니다.
서버에 요청을 전송하기 위해 브라우저는 주소창을 제공합니다.
브라우저의 주소창에 URL을 입력하고 엔터키를 누르면 URL의 호스트 이름이 DNS를 통해 IP 주소로 변환되고 이 IP 주소로 갖는 서버에게 요청을 전송하게 됩니다.

스킴과 호스트만으로 구성된 URI에 의한 요청이 서버로 전송되면,
루트 요청에는 명확히 리소스를 요청하는 내용이 없지만 일반적으로 서버는 루트 요청에 대해 암묵적으로 index.html을 응답하도록 기본 설정이 되어있습니다.
따라서 https://velog.io/@imddoy/posts는 https://velog.io/@imddoy/posts/index.html이 되겠네요!
서버는 루트 요청에 대해 서버의 루트 폴더에 존재하는 정적 파일 index.html을 클라이언트(브라우저)로 응답합니다.
만약 index.html이 아닌 다른 정적 파일을 서버에 요청하려면 브라우저의 주소창에 https://velog.io/@imddoy/assets/data/data.json과 같이 요청할 정적 파일의 경로(서버의 루트 폴더 기준)와 파일 이름을 URI의 호스트 뒤이 패스에 기술하여 서버에 요청합니다.
이후 서버는 루트 폴더의 assets/data 폴더 내에 있는 정적 파일 data.json을 응답합니다.
그렇다고 정적 데이터만 요청할 수 있는 것은 아닙니다.
자바스크립트를 통해 동적으로 서버에 정적/동적 데이터를 요청할 수 있습니다.
이러한 요청과 응답은 어디서 보나요?
요청과 응답은 개발자 도구의 Network 패널에서 확인 가능합니다.
그런데 index.html말고도 응답을 받아오네요..?
Network를 확인해보면 index.html뿐만 아니라 css, js,이미지,폰트파일들도 응답되어있습니다.
왜 요청도 하지 않은 이 리소스들이 응답이 되어있을까요?
브라우저의 렌더링 엔진이 HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그, 즉 css 파일을 로드하는 link 태그, 이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그들을 만나면 html의 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청하기 때문입니다!
HTTP는 1989년 HTML, URL과 함께 팀 버너스 리 경이 고안한 웹에서 브라우저와 서버가 통신하기 위한 프로토콜입니다.
HTTP1.1는 커넥션당 하나의 요청과 응답만 처리합니다.
여러 개의 요청을 한번에 전송할 수 없고 응답도 마찬가지인데요..!
HTML 문서 내에 포함된 여러 개의 리소스 요청, 즉 css 파일을 로드하는 link태그, 이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그 등에 의한 리소스 요청이 개별적으로 전송되고 응답 또한 개별적으로 전송합니다.
리소스의 동시 전송이 불가능한 구조이므로 요청할 리소스의 개수에 비례하여 응답시간도 증가하는 단점이 있습니다.
이에 반해 HTTP2.0은 다중 요청/응답이 가능합니다.
여러 리소스의 동시 전송 가능하므로 http1.1보다 페이지 로드 속도가 약 50% 정도가 빠르다고 알려졌습니다.
HTTP3.0
HTTP3.0은 완전히 새로운 기반 위에서 동작합니다.
기존 TCP 대신 UDP + QUIC 프로토콜을 사용합니다.
HTTP2.0의 등장과 함께 기존의 프로토콜 데이터 체계를 프레임과 스트림 개념으로 재구축한 결과 기존보다 혁신적으로 성능이 향상되었지만, 여전히 HTTP는 TCP 기반 위에서 동작되기 때문에, TCP는 핸드셰이크 과정에서 지연이 발생하고, 패킷 손실 시 재전송되면서 전체 스트림이 지연되는 HOLB(Head-of-Line Blocking) 문제가 여전히 존재했습니다.
UDP 기반인 QUIC 프로토콜이 새롭게 고안되었습니다. 그리고 이 새로운 QUIC 프로토콜이 TCP/IP 4계층에도 동작시키기 위해 설계된 것이 바로 HTTP 3.0입니다.
연결 속도가 빠르고 지연에 강하며 보안을 기본으로 내장한 프로토콜입니다.
| 프로토콜 | 기반 | 다중 요청 | HOL Blocking 해결 | 보안 | 속도 |
|---|---|---|---|---|---|
| HTTP/1.1 | TCP | X | X | 선택 | 느림 |
| HTTP/2.0 | TCP | O | 부분 해결 | 선택 | 중간 |
| HTTP/3.0 | UDP (QUIC) | O | 완전 해결 | 기본 HTTPS | 빠름 |
브라우저의 요청에 의해 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트입니다.
순수한 텍스트인 HTML 문서를 브라우저에 시각적인 픽셀로 렌더링하려면 html 문서를 브라우저가 이해할 수 있는 자료구조로 변환하여 메모리에 저장합니다.
브라우저의 렌더링 엔진은 응답받은 html 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성합니다.

서버에 존재하던 html 파일이 브라우저의 요청에 의해 응답
서버는 요청받은 html 파일을 메모리에서 읽어 바이트 형태로 변환하고, 이를 인터넷을 통해 브라우저에 응답
브라우저는 서버가 응답한 html 문서를 바이트 형태로 응답
응답된 바이트 형태의 html 문서는 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 변환
meta 태그의 charset 어트리뷰트에 선언된 인코딩 방식은 content-type: text/html; charset=utf-8과 같이 응답 헤더에 담겨 응답
브라우저는 이를 확인하고 문자열로 변환
문자열로 변환된 html 문서를 읽어들여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해
각 토큰들을 객체로 변환하여 노드를 생성
토큰의 내용에 따라 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드가 생성
노드는 이후 DOM을 구성하는 기본 요소가됨
html 문서는 html 요소들의 집합으로 이루어지며 html 요소는 중첩 관계를 가짐
html 요소의 콘텐츠 영역에는 텍스트뿐만 아니라 다른 html 요소도 포함될 수 있음
html 요소 간에는 중첩 관계에 의해 부자 관계가 형성
이러한 html 요소 간의 부자 관계를 반영하여 모든 노드들을 트리 자료구조로 구성
이 노드들로 구성된 트리 자료구조를 dom이라 부름
➡️ DOM은 HTML 문서를 파싱한 결과물입니다.
렌더링 엔진은 html을 처음부터 한줄씩 순차적으로 파싱하여 DOM을 생성합니다.
이처럼 렌더링 엔진은 DOM을 생성해 나가다가 CSS 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단합니다.
link 태그의 href 어트리뷰트에 지정된 css 파일을 서버에 요청하여 로드한 css 파일이나 style태그 내의 css를 html과 동일한 파싱 과정(바이트 → 문자 → 토큰 → 노드 → cssom)을 거치며 해석하여 cssom을 생성합니다.
이후 css 파싱을 완료하면 html 파싱이 중단된 지점부터 다시 html을 파싱하기 시작하여 DOM 생성을 재개합니다.
렌더링 엔진은 meta 태그까지 html을 순차적으로 해석한 다음 link 태그를 만나면 dom 생성을 일시 중단하고 link 태그의 href어트리뷰트에 지정된 css 파일을 서버에 요청합니다.
서버로부터 css파일이 응답되면 렌더링 엔진은 html과 동일한 해석 과정을 거쳐 css를 파싱하여 cssom을 생성 합니다.
cssom은 css의 상속을 반영하여 생성합니다.

렌더링 엔진은 서버로부터 응답된 html과 css를 파싱하여 각각 dom과 cssom를 생성하는데,
DOM과 CSSOM은 렌더링을 위해 렌더 트리로 결합하게 됩니다.
렌더트리는 렌더링을 위한 트리 구조의 자료구조로, 브라우저 화면에 렌더링되지 않는 노드와 css에 의해 비표되는 노드들은 포함하지 않습니다.
렌더 트리는 브라우저 화면에 렌더링되는 노드만으로 구성됩니다.

렌더 트리는 각 html 요소의 레이아웃을 계산하는데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력합니다.

➡️ 위의 변화가 발생하면 레이아웃 계산과 페인팅 재차 실행하게 됩니다.
그러나 레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이 드는,
즉 성능에 악영향을 주는 작업이기 때문에 가급적 리렌더링이 빈번하게 발생하지 않도록 주의할 필요가 있습니다.
html 문서를 파싱한 결과물로서 생성된 DOM은 HTML 문서의 구조와 정보뿐만 아니라 HTML 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스로서 DOM api를 제공합니다.
자바스크립트 코드에서 DOM api를 사용하면 이미 생성된 DOM을 동적으로 조작 가능합니다.
css 파싱 과정과 마찬가지로 렌더링 엔진은 html을 한 줄씩 순차적으로 파싱하며 DOM을 생성해 나가다가 자바스크립트 파일을 로드하는 script 태그나 자바스크립트 코드를 콘텐츠로 담은 script 태그를 만나면 DOM 생성을 일시 중단합니다.
이때 자바스크립트 코드를 파싱하는 것은 자바스크립트 엔진인데요,
자바스크립트 파싱과 실행이 종료되면 렌더링 엔진으로 다시 제어권 넘겨 html 파싱이 중단된 지점부터 다시 html 파싱을 시작하여 DOM 생성 재개합니다.
렌더링 엔진이 html,css를 파싱하여 DOM과 CSSOM을 생성하듯이 자바스크립트 엔진은 자바스크립트를 해석하여 AST(추상적 구문 트리)를 생성합니다.
AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드를 생성하여 실행합니다.

자바스크립트 파싱에 의한 html 파싱 중단
script 태그의 위치에 따라 html 파싱이 블로킹되어 DOM 생성이 지연될 수 있습니다.
body 요소의 가장 아래에 자바스크립트를 위치시키는 것이 좋은데요, 그 이유는 아래와 같습니다.
1️⃣ DOM이 완성되지 않은 상태에서 자바스크립트가 DOM을 조작하면 에러가 발생할 수 있음
2️⃣ 자바스크립트 로딩/파싱/실행으로 인해 html 요소들의 렌더링에 지장받는 일이 발생하지 않아 페이지 로딩 시간이 단축
script 태그의 async/defer 어트리뷰트
DOM 생성이 중단되는 문제를 근본적으로 해결하기 위해 HTML5부터는 script 태그에 async와 defer 어트리뷰트가 추가되었습니다.
src 어트리뷰트를 통해 외부 자바스크립트 파일을 로드하는 경우에만 사용 가능합니다.<script async src="extern.js"></script> <script defer src="extern.js"></script>
- async
- html 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행
- 자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되며 이때 html 파싱이 중단
- 여러 개의 script 태그에 async 어트리뷰트를 지정하면 script 태그의 순서와는 상관없이 로드가 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지 않음
- 따라서 순서 보장이 필요한 script 태그에는 async 어트리뷰트를 지정하지 않아야함- defer
- html 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행
- 자바스크립트의 파싱과 실행은 html 파싱이 완료된 직후, 즉 DOM 생성이 완료된 직후 진행
- DOM 생성이 완료된 이후 실행되어야 할 자바스크립트에 유용

자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 dom이나 cssom이 변경됩니다.
이때 변경된 dom과 cssom은 다시 렌더트리로 결합되고 변경된 렌더트리를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링하게됩니다.
리플로우와 리페인트가 반드시 순차적으로 동시에 실행되는 것은 아니며,
레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행합니다.
positioning보다는transform레이아웃 계산과 페인팅을 다시 실행하는 것은 비용이 큽니다!!
위에서 말했듯이 비용이 큰 작업이기 때문에 리플로우가 발생하지 않도록 주의가 필요합니다.
따라서 단순히 위치만 조정하는 애니메이션에 transform을 사용해야 합니다.
transform은 레이아웃은 변하지 않고 요소 자체의 위치만 바꿀 수 있지만
positioning은 요소가 이동함에 따라 레이아웃 자체가 바뀌게 됩니다.
transform: translate는 GPU 가속을 이용합니다. GPU에서 처리되므로 브라우저가 레이아웃을 다시 계산할 필요가 없어 리플로우가 발생하지 않습니다.
반면에position,top/left등은 CPU 중심의 레이아웃 변경을 유발합니다.
요소의 위치나 크기가 바뀌기 때문에 레이아웃 재계산이 필요하게 됩니다.
브라우저의 렌더링 엔진은 html 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM을 생성합니다.
DOM은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어할 수 있는 API, 프로퍼티와 메서드를 제공하는 트리 구조입니다.

html 요소 간에는 중첩 관계에 의해 계층적인 부자 관계가 형성됩니다.
이러한 html 요소간의 부자 관계를 반영하여 HTML 문서의 구성 요소인 HTML 요소를 객체화한 모든 노드 객체들을 트리 자료 구조로 구성됩니다.
노드들의 계층 구조로 이뤄진 트리 자료구조는
부모 노드와 자식 노드로 구성되어 노드 간의 계층적 구조를 표현하는 자료구조입니다.
노드 객체들로 구성된 트리 자료구조를 DOM이라고 하고, DOM 트리라고 부르기도 합니다.

모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받습니다.
문서 노드는 Document, HTMLDocument 인터페이스를 상속바독 어트리뷰트 노드는 Attr, 텍스트 노드는 CharacterData 인터페이스를 각각 상속합니다.
요소 노드는 Element 인터페이스를 상속받습니다.
추가적으로 HTMLElement와 태그의 종류별로 세분화된 HTMLHtmlEIement, HTMLHeadEIement HTMLBodyEIement, HTMLUListEIement 등의 인터페이스를 상속받습니다.
실제 노드 객체의 상속구조가 궁금하다면??
노드 객체의 상속구조는 개발자 도구의 Elements 패널 우측의 Properties 패널에서 확인 가능합니다!
노드 객체는 공통된 기능일수록 프로토타입 체인의 상위에,
개별적인 고유 기능일수록 프로토타입 체인의 하위에 프로토타입 체인을 구축하여
노드 객체에 필요한 기능, 즉 프로퍼티와 메서드를 제공하는 상속구조를 가집니다.
**DOM은 HTML 문서의 계층적 구조와 정보를 표현하는 것은 물론 노드 객체의 종류,
즉 노드 타입에 따라 필요한 기능을 프로퍼티와 메서드의 집합인 DOM API로 제공합니다.
이 DOM API를 통해 HTML의 구조나 내용 또는 스타일 등을 동적으로 조작할 수 있습니다.**
DOM(Document Object Model) 조작이란 웹 페이지의 문서 구조에 접근하여 그 구조를 변경하는 것을 말합니다.
이는 새로운 요소를 생성하여 DOM에 추가하거나, 기존 요소를 삭제 또는 교체하는 작업을 포함합니다.
DOM 조작을 통해 페이지의 콘텐츠, 구조, 스타일 등을 동적으로 변경할 수 있습니다.
하지만, DOM 조작은 리플로우 및 리페인트를 유발하여 성능에 영향을 줄 수 있으므로,
복잡한 콘텐츠를 다룰 때는 성능 최적화를 위해 주의가 필요합니다.
Element.prototype.innerHTML 프로퍼티는 요소 노드의 HTML 마크업을 취득하거나 변경할 수 있는 접근자 프로퍼티입니다.
이 프로퍼티를 사용하면 요소 노드의 콘텐츠 영역 내에 포함된 모든 HTML 마크업을 문자열로 반환하거나, 새로운 HTML 마크업 문자열을 할당하여 요소 노드의 자식 노드로 DOM에 반영할 수 있습니다.
예를 들어, 다음과 같이 innerHTML 프로퍼티를 사용하여 DOM 조작을 할 수 있습니다
<!DOCTYPE html>
<html>
<body>
<div id="foo">Hello <span>world!</span></div>
</body>
<script>
// #foo 요소의 콘텐츠 영역 내의 HTML 마크업을 문자열로 취득
console.log(document.getElementById("foo").innerHTML);
// "Hello <span>world!</span>"
// HTML 마크업 문자열을 할당하여 요소 노드의 자식 노드로 DOM에 반영
document.getElementById("foo").innerHTML = "Hi <span>there!</span>";
</script>
</html>
실행 결과
Hello world!
하지만, 사용자로부터 입력받은 데이터를 innerHTML 프로퍼티에 직접 할당하는 것은 크로스 사이트 스크립팅(XSS) 공격에 취약할 수 있으므로 주의가 필요합니다.
Element.prototype.insertAdjacentHTML 메서드는 기존 요소를 제거하지 않으면서 지정된 위치에 새로운 요소를 삽입할 수 있습니다.
이 메서드는 첫 번째 인수로 전달한 위치에 따라 새로운 요소를 삽입하고, 두 번째 인수로 전달한 HTML 마크업 문자열을 파싱하여 생성된 노드를 DOM에 반영합니다.
<!DOCTYPE html>
<html>
<body>
<div id="foo">
text
</div>
</body>
<script>
const $foo = document.getElementById('foo');
$foo.insertAdjacentHTML('beforebegin', '<p>beforebegin</p>');
$foo.insertAdjacentHTML('afterbegin', '<p>afterbegin</p>');
$foo.insertAdjacentHTML('beforeend', '<p>beforeend</p>');
$foo.insertAdjacentHTML('afterend', '<p>afterend</p>');
</script>
</html>
실행 결과
text
insertAdjacentHTML 메서드는 기존 요소에 영향을 주지 않고 새로운 요소만을 추가하는 방식으로 작동하기 때문에, innerHTML 프로퍼티를 사용하는 것보다 효율적일 수 있습니다.
그러나 이 방법 역시 XSS 공격에 취약할 수 있으므로, 사용자 입력을 처리할 때는 주의가 필요합니다.
지금까지 살펴본 innerHTML 프로퍼티와 insertAdjacentHTML 메서드는 HTML 마크업 문자열을 파싱하여 노드를 생성하고 DOM에 반영합니다.
DOM은 노드를 직접 생성/삽입/삭제/치환하는 메서드도 제공합니다.
<!DOCTYPE html>
<html>
<body>
<ul id="fruits"><li>Apple</li></ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
// 1. 요소 노드 생성
const $li = document.createElement("li");
// 2. 텍스트 노드 생성
const textNode = document.createTextNode("Banana");
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
</script>
</html>
실행결과
- Apple
<!DOCTYPE html>
<html>
<body>
<ul id="fruits"></ul>
</body>
<script>
const $fruits = document.getElementById("fruits");
["Apple", "Banana", "Orange"].forEach((text) => {
// 1. 요소 노드 생성
const $li = document.createElement("li");
// 2. 텍스트 노드 생성
const textNode = document.createTextNode(text);
// 3. 텍스트 노드를 $li 요소 노드의 자식 노드로 추가
$li.appendChild(textNode);
// 4. $li 요소 노드를 #fruits 요소 노드의 마지막 자식 노드로 추가
$fruits.appendChild($li);
});
</script>
</html>
실행 결과
<!DOCTYPE html>
<html><body><ul id="fruits"><li>Apple</li><li>Banana</li></ul></body><script>
const $fruits = document.getElementById("fruits");
// 요소 노드 생성
const $li = document.createElement("li");
// 텍스트 노드를 $li 요소 노드의 마지막 자식 노드로 추가
$li.appendChild(document.createTextNode("Orange"));
// $li 요소 노드를 #fruits 요소 노드의 마지막 자식 요소(Banana) 앞에 삽입
$fruits.insertBefore($li, $fruits.lastElementChild);
// Apple - Orange - Banana
</script></html>
실행결과
- Apple
- Banana
<!DOCTYPE html>
<html><body><ul id="fruits"><li>Apple</li><li>Banana</li><li>Orange</li></ul></body><script>
const $fruits = document.getElementById('fruits');
// 이미 존재하는 요소 노드를 취득
const [$apple, $banana, ] = $fruits.children;
// 이미 존재하는 $apple 요소 노드를 #fruits 요소 노드의 마지막 노드로 이동
$fruits.appendChild($apple); // Banana - Orange - Apple
// 이미 존재하는 $banana 요소 노드를 #fruits 요소의 마지막 자식 노드 앞으로 이동
$fruits.insertBefore($banana, $fruits.lastElementChild);
// Orange - Banana - Apple
</script></html>
실행 결과
- Apple
- Banana
- Orange
Node.prototype.cloneNode([deep: true | false]) 메서드는 노드의 사본을 생성하여 반환합니다.
매개변수 deep에 true를 인수로 전달하면 노드를 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성하고, false를 인수로 전달하거나 생략하면 노드를 얕은 복사하여 노드 자신만의 사본을 생성합니다.
얕은 복사로 생성된 요소 노드는 자손 노드를 복사하지 않으므로 텍스트 노드도 없습니다.
<html><body><ul id="fruits"><li>Apple</li></ul></body><script>
const $fruits = document.getElementById("fruits");
const $apple = $fruits.firstElementChild;
// $apple 요소를 얕은 복사하여 사본을 생성. 텍스트 노드가 없는 사본이 생성된다.
const $shallowClone = $apple.cloneNode();
// 사본 요소 노드에 텍스트 추가
$shallowClone.textContent = "Banana";
// 사본 요소 노드를 #fruits 요소 노드의 마지막 노드로 추가
$fruits.appendChild($shallowClone);
// #fruits 요소를 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성
const $deepClone = $fruits.cloneNode(true);
// 사본 요소 노드를 #fruits 요소 노드의 마지막 노드로 추가
$fruits.appendChild($deepClone);
</script></html>
실행 결과
- Apple
Node.prototype.replaceChild(newChild, oldChild) 메서드는 자신을 호출한 노드의 자식 노드를 다른 노드로 교체합니다.
첫 번째 매개변수 newChild에는 교체할 새로운 노드를 인수로 전달하고, 두 번째 매개변수 oldChild에는 이미 존재하는 교체될 노드를 인수로 전달합니다.
oldChild 매개변수에 인수로 전달한 노드는 replaceChild 메서드를 호출한 노드의 자식 노드이어야 합니다.
<!DOCTYPE html>
<html><body><ul id="fruits"><li>Apple</li></ul></body><script>
const $fruits = document.getElementById("fruits");
// 기존 노드와 교체할 요소 노드를 생성
const $newChild = document.createElement("li");
$newChild.textContent = "Banana";
// #fruits 요소 노드의 첫 번째 자식 요소 노드를 $newChild 요소 노드로 교체
$fruits.replaceChild($newChild, $fruits.firstElementChild);
</script></html>
실행결과
- Apple
Node.prototype.removeChild(child) 메서드는 child 매개변수에 인수로 전달한 노드를 DOM에서 삭제합니다.
<!DOCTYPE html>
<html><body><ul id="fruits"><li>Apple</li><li>Banana</li></ul></body><script>
const $fruits = document.getElementById("fruits");
// #fruits 요소 노드의 마지막 요소를 DOM에서 삭제
$fruits.removeChild($fruits.lastElementChild);
</script></html>
실행 결과
- Apple
- Banana
웹 브라우저는 단순히 HTML 파일을 받아서 보여주는 도구가 아니라, HTML, CSS, JavaScript를 파싱하고 분석하여 시각적으로 표현하는 정교한 렌더링 시스템입니다.
렌더링 과정에서는 DOM, CSSOM, 렌더 트리, 자바스크립트 실행 등 다양한 요소가 유기적으로 작동하며, 이 중 하나라도 병목이 생기면 전체 페이지 성능에 영향을 줄 수 있습니다.
특히 JavaScript를 통한 DOM 조작은 유연하고 강력한 기능이지만, 리플로우와 리페인트를 유발해 성능 저하로 이어질 수 있으므로 신중하게 사용해야 합니다.
또이님 이번 주도 수고하셨습니다.
브라우저 렌더링 과정을 도식화한 그림을 많이 첨부해주셔서 확실히 이해하기 편했던 것 같아요. 리플로우와 리페인트가 빈번히 발생할 경우 성능에 안 좋다는 건 알고 있었는데 CSS를 적용시킬 때 positioning 보다 transform을 사용해서 리플로우 재발생을 방지할 수 있는 건 몰랐네요! 최적화 방법을 확실하게 짚어주셔서 감사합니다.
HTTP 프로토콜 관련 내용은 제가 놓친 부분인데 HTTP의 발전 과정과 HTTP1.1, HTTP2.0, HTTP3.0의 차이점을 잘 정리해주셔서 좋았습니다.
잘 읽었습니다!
안녕하세요 또이님 :)
요새 여러 가지 이슈로 많이 바쁘시다고 들었는데 그래도 성실히 참여해주셔서 너무 감사합니다...🥹
브라우저 렌더링 과정을 한 눈에 정리해주셔서 다시 복습하기 너무 좋았고, 리플로우 리페인트 차이점 설명해주신 부분이 최적화를 생각할 때 고려하면 좋겠다는 생각이 들었습니다.
이번 주차도 수고 많으셨습니다!