🔰브라우저의 렌더링 과정
클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 브라우저 환경을 고려해야 더 효율적인 프로그래밍이 가능하다.
브라우저는 다음과 같은 과정을 거쳐 렌더링을 수행한다.
- 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다.
- 브라우저 렌더링 엔진은 서버로부터 응답받은 HTML과 CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.
- 브라우저 자바스크립트 엔진은 서버로부터 응답받은 자바스크립트를 파싱하여 AST(Abstract Syntax Tree)를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
- 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다.
🍎 파싱(parsing)
파싱은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서의 문자열을 토큰으로 분해하고 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스 트리(parse tree/ syntax tree)를 생성하는 과정을 말한다.
일반적으로 파싱이 완료된 이후에는 파스 트리를 기반으로 intermediate code인 바이트코드(bytecode)를 생성하고 실행한다.
🍎 렌더링(rendering)
렌더링은 HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말한다.
요청과 응답
렌더링에 필요한 리소스(HTML, CSS, JS 등)는 모두 서버에 존재하므로 브라우저가 서버에 필요한 리소스를 요청하고 응답받은 리소스를 파싱하여 렌더링한다.
브라우저의 주소창에 URL을 입력하고 엔터 키를 누르면 URL의 hostname이 DNS를 통해 IP 주소로 변환되고 해당 IP 주소를 갖는 서버에게 요청한다.
일반적으로 서버는 루트 요청에는 암묵적으로 index.html을 응답하도록 설정되어 있다.
정적 파일뿐만 아니라 정적/동적 데이터를 요청할 수도 있다.
HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그, 즉 CSS 파일을 로드하는 link 태그나 이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그 등을 만나면 HTML의 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청한다.
HTTP 1.1과 HTTP 2.0
HTTP(HyperText Transfer Protocol)는 웹에서 브라우저와 서버가 통신하기 위한 프로토콜이다.
HTTP/1.1은 기본적으로 커넥션당 하나의 요청과 응답만 처리한다. 요청할 리소스의 개수에 비례하여 응답 시간이 증가하는 단점이 있다.
HTTP/2는 커넥션당 여러 개의 요청과 응답이 가능하다. 여러 리소스의 동시 전송이 가능하므로 HTTP/1.1에 비해 페이지 로드 속도가 약 50% 정도 빠르다고 알려져 있다.
HTML 파싱과 DOM 생성
서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트다. 따라서 브라우저가 이해할 수 있는 자료구조(객체)로 변환하여 메모리에 저장해야 한다.
브라우저 렌더링 엔진은 응답받은 HTML 문서를 파싱하여 브라우저가 이해할 수 있는 자료구조인 DOM(Document Object Model)을 생성한다.
- 서버가 응답한 HTML 문서는 바이트 형태다. 이 문서는 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 변환된다. 해당 인코딩 방식은 응답 헤더에 담겨 있고, 브라우저가 이를 보고 문자열로 변환한다.
- 문자열로 변환된 HTML 문서를 토큰(문법적 의미를 갖는 코드의 최소 단위)으로 분해한다.
- 각 토큰들을 객체로 변환하여 노드를 생성한다. 토큰의 내용에 따라 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드가 생성된다. 노드는 DOM을 구성하는 기본 요소가 된다.
- HTML 문서는 HTML 요소들의 집합으로 이루어지며 HTML 요소는 중첩 관계를 갖는다. 이러한 관계를 반영하여 모든 노드들을 트리 자료구조로 구성한다. 이 자료구조를 DOM이라 한다.
CSS 파싱과 CSSOM 생성
브라우저 렌더링 엔진은 HTML을 한줄씩 순차적으로 파싱하여 DOM을 생성한다. 이 과정 중에 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단한다.
- link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 파싱 과정(바이트 -> 문자 -> 토큰 -> 노드 -> CSSOM)을 거치며 CSSOM(CSS Object Model)을 생성한다. CSSOM은 CSS의 상속을 반영하여 생성된다.
- CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하며 DOM 생성을 재개한다.
렌더 트리 생성
브라우저 렌더링 엔진은 서버로부터 응답받은 HTML과 CSS를 파싱하여 각각 DOM과 CSSOM 트리를 생성한다.
DOM과 CSSOM은 렌더링을 위한 렌더 트리로 결합된다.
렌더 트리에는 브라우저 화면에 렌더링되지 않는 노드(meta 태그, script 태그 등)와 CSS에 의해 비표시(display: none 등)되는 노드들은 포함되지 않는다. 즉, 브라우저 화면에 렌더링되는 노드만으로 구성된다.
완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치 및 크기)을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다.
브라우저 렌더링 과정은 반복해서 실행된다.
- 자바스크립트에 의한 노드 추가 또는 삭제
- 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
- HTML 요소의 레이아웃에 변경을 발생시키는 width, height, margin, padding, border, display, position, top, right, bottom, left 등의 스타일 변경
레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 성능에 악영향을 주는 작업이다. 가급적 발생하지 않도록 주의해야 한다.
자바스크립트 파싱과 실행
자바스크립트 파싱과 실행은 브라우저 렌더링 엔진이 아닌 자바스크립트 엔진이 처리한다.
브라우저 렌더링 엔진은 HTML을 한줄씩 순차적으로 파싱하여 DOM을 생성한다. 이 과정 중에 자바스크립트 파일을 로드하는 script 태그나 자바스크립트 코드를 콘텐츠로 담은 script 태그를 만나면 DOM 생성을 일시 중단한다. script 태그의 src 어트리뷰트에 정의된 자바스크립트 파일을 서버에 요청하여 로드한 자바스크립트 파일이나 script 태그 내의 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다.
자바스크립트 엔진은 자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 low-level 언어로 변환하고 실행한 후 AST(Abstract Syntax Tree; 추상 구문 트리)를 생성한다. 그리고 AST를 기반으로 인터프리터가 실행할 수 있는 intermediate code인 바이트코드를 생성하여 실행한다.
- 토크나이징(tokenizing):단순 문자열인 자바스크립트 소스코드를 분석하여 토큰으로 분해한다.
- 파싱: 토큰들을 분석하여 AST를 생성한다. AST는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조다.
- 바이트코드 생성과 실행: AST는 인터프리터가 실행할 수 있는 중간 코드인 바이트코드로 변환되고 인터프리터에 의해 실행된다.
리플로우와 리페인트
변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 리렌더링된다. 이를 리플로우(reflow)와 리페인트(repaint)라 한다.
리플로우는 레이아웃 계산을 다시 하는 것이고, 노드 추가 및 삭제, 요소의 크기 또는 위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 실행된다.
리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트를 하는 것을 말한다.
리플로우와 리페인트가 반드시 순차적으로 동시에 실행되는 것은 아니다. 레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행된다.
자바스크립트 파싱에 의한 HTML 파싱 중단
브라우저 렌더링 엔진과 자바스크립트 엔진은 직렬적으로 파싱을 수행한다. 즉, 동기적으로 순차적으로 HTML, CSS, 자바스크립트를 파싱하고 실행한다.
script 태그를 body 요소의 가장 아래에 위치시키는 것은 좋은 아이디어다.
- 자바스크립트 코드에서 DOM API를 사용할 경우 DOM이나 CSSOM이 이미 생성되어 있어서 에러가 발생하지 않는다.
- 자바스크립트 로딩, 파싱 및 실행으로 인해 HTML 요소들의 렌더링에 지장받는 일이 발생하지 않아 페이지 로딩 시간이 단축된다.
script 태그의 async/defer 어트리뷰트
자바스크립트 파싱에 의해 DOM 생성이 블로킹되는 문제를 해결하기 위해 HTML5부터 추가되었다.
src 어트리뷰트를 통해 외부 자바스크립트 파일을 로드하는 경우에만 사용할 수 있다. 인라인 자바스크립트에는 사용할 수 없다.
aysnc와 defer 어트리뷰트를 사용하면 HTML 파싱과 외부 자바스크립트 파일의 로딩이 비동기적으로 동시에 진행된다.
async | defer |
자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되고, 이때 HTML 파싱이 중단된다. 여러 개의 script 태그에 async 어트리뷰트를 지정하면 script 태그 순서와 상관없이 로드가 완료된 자바스크립트부터 먼저 실행된다. 즉, 순서가 보장되지 않는다. |
자바스크립트 파싱과 실행은 HTML 파싱이 완료된 직후, DOM 생성이 완료된 직후(DOMContentLoaded 이벤트 발생) 진행된다. |
🔰DOM
DOM(Document Object Model)은 HTML 문서의 계층적 구조와 정보를 표현하며 이를 제어하는 API(프로퍼티와 메서드)를 제공하는 트리 자료구조다.
노드
HTML 요소(HTML element)는 HTML 문서를 구성하는 개별적인 요소를 의미한다. 시작 태그, 어트리뷰트 이름, 어트리뷰트 값, 콘텐츠, 종료 태그로 이루어져 있다.
HTML 요소는 렌더링 엔진에 의해 파싱되어 DOM을 구성하는 요소 노드 객체로 변환된다. 이때 HTML 요소의 어트리뷰트는 어트리뷰트 노드, 텍스트 콘텐츠는 텍스트 노드로 변환된다.
HTML 문서는 HTML 요소의 집합이며, HTML 요소는 서로 중첩 관계를 갖는다. 이러한 관계를 반영하여 HTML 요소를 객체화한 모든 노드 객체들을 트리 자료구조(DOM)로 구성한다.
노드 객체는 총 12개의 타입이 있다. 중요한 노드 타입은 다음과 같이 4가지이다.
노드 타입 | 설명 |
문서 노드(document node) | DOM 트리의 최상위에 존재하는 루트 노드로서 document 객체를 가리킨다. 다른 노드들에 접근하려면 문서 노드를 통해야 한다. |
요소 노드(element node) | HTML 요소를 가리키는 객체다. 문서의 구조를 표현한다. |
어트리뷰트 노드(attribute node) | HTML 요소의 어트리뷰트를 가리키는 객체다. 어트리뷰트가 지정된 HTML 요소의 요소 노드와만 연결되어 있다. 따라서 어트리뷰트를 참조하거나 변경하려면 먼저 요소 노드에 접근해야 한다. |
텍스트 노드(text node) | HTML 요소의 텍스트를 가리키는 객체다. 문서의 정보를 표현한다. 요소 노드의 자식 노드이자 리프 노드다. 따라서 텍스트 노드에 접근하려면 먼저 부모 노드인 요소 노드에 접근해야 한다. |
🍎 document 객체
브라우저가 렌더링한 HTML 문서 전체를 가리키는 객체이다.
전역 객체 window의 document 프로퍼티에 바인딩되어 있다. 따라서 window.document 또는 document로 참조할 수 있다.
브라우저 환경의 모든 자바스크립트 코드는 script 태그에 의해 분리되어 있어도 하나의 전역 객체를 공유한다. 따라서 document 객체도 HTML 문서당 유일하다.
위 4가지 타입 외에도 주석을 위한 Comment 노드, DOCTYPE을 위한 DocumentType 노드, 복수의 노드를 생성하여 추가할 때 사용하는 DocumentFragment 노드 등이 있다.
DOM을 구성하는 노드 객체는 ECMAScript 사양에 정의된 표준 빌트인 객체가 아니라 브라우저 환경에서 추가적으로 제공하는 호스트 객체다. 하지만 노드 객체도 자바스크립트 객체이므로 프로토타입에 의한 상속 구조를 갖는다.
모든 노드 객체는 Object, EventTarget, Node 인터페이스를 상속받는다. 문서 노드는 Document, HTMLDocument 인터페이스를 상속받고 어트리뷰트 노드는 Attr, 텍스트 노드는 CharacterData 인터페이스를 상속받는다. 요소 노드는 Element 인터페이스를 상속받는다. 또한 태그의 종류별로 세분화된 HTMLHtmlElement, HTMLHeadElement 등의 인터페이스를 상속받는다.
이벤트와 관련된 기능은 EventTarget 인터페이스가 제공하므로 모든 노드 객체는 이벤트를 발생시킬 수 있다. 또한 노드 탐색 및 정보 제공 기능은 Node 인터페이스가 제공한다.
공통된 기능일수록 프로토타입 체인의 상위에, 개별적인 고유 기능일수록 프로토타입 체인 하위에 존재한다.
DOM은 HTML 문서의 계층적 구조와 정보를 표현하고 노드 타입에 따라 필요한 기능을 DOM API로 제공하여 HTML의 구조 및 내용, 스타일을 동적으로 조작할 수 있게 한다.
요소 노드 취득
HTML을 동적으로 조작하기 위해 먼저 요소 노드를 취득해야 한다.
메서드 | 설명 |
Document.prototype.getElementById | 인자로 전달한 id 어트리뷰트 값을 갖는 하나의 요소 노드를 탐색하여 반환한다. 중복된 id 값을 갖는 HTML 요소가 여러 개 있는 경우 첫 번째 요소 노드만 반환한다. 요소가 존재하지 않을 경우 null을 반환한다. id 값은 유일해야 하며 여러 개를 가질 수 없다. 단, 중복된 id 값을 갖는 HTML 요소가 존재해도 에러가 발생하지 않는다. HTML 요소에 id 어트리뷰트를 부여하면 id 값과 동일한 이름의 전역 변수가 암묵적으로 선언되고 해당 노드 객체가 할당되는 부수 효과가 있다. 단 id 값과 동일한 이름의 전역 변수가 있으면 노드 객체가 할당되지 않는다. |
Document/Element.prototype.getElementsByTagName | 인자로 전달한 태그 이름을 갖는 모든 요소 노드를 탐색하여 HTMLCollection 객체를 반환한다. 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환한다. HTML 문서의 모든 요소 노드를 취득하려면 '*'을 인자로 전달한다. |
Document/Element.prototype.getElementsByClassName | 인자로 전달한 class 어트리뷰트 값을 갖는 모든 요소 노드들을 탐색하여 HTMLCollection 객체를 반환한다. 요소가 존재하지 않는 경우 빈 HTMLCollection 객체를 반환한다. 인자로 공백으로 구분하여 여러 개의 class를 지정할 수 있다. |
Document/Element.prototype.querySelector | 인자로 전달한 CSS 선택자를 만족시키는 하나의 요소 노드를 탐색하여 반환한다. 만족하는 요소 노드가 여러 개일 경우 첫 번째 요소 노드만 반환한다. 존재하지 않는 경우 null을 반환한다. 인자로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생한다. |
Document/Element.prototype.querySelectorAll | 인자로 전달한 CSS 선택자를 만족시키는 모든 요소 노드를 탐색하여 NodeList 객체를 반환한다. 존재하지 않는 경우 빈 NodeList 객체를 반환한다. 인자로 전달한 CSS 선택자가 문법에 맞지 않는 경우 DOMException 에러가 발생한다. HTML 문서의 모든 요소 노드를 취득하려면 '*'을 인자로 전달한다. |
Element.prototype.matches | 인자로 전달한 CSS 선택자를 통해 특정 요소 노드를 취득할 수 있는지 여부를 의미하는 boolean 값을 반환한다. 이벤트 위임을 사용할 때 유용하다. |
🍎 HTMLCollection과 NodeList
DOM 컬렉션 객체인 HTMLCollection과 NodeList는 DOM API가 여러 개의 결과값을 반환하기 위한 DOM 컬렉션 객체다. 모두 유사 배열 객체이면서 이터러블이다.
HTMLCollection은 노드 객체의 상태 변화를 실시간으로 반영하는 live 객체로 동작한다. 따라서 이 객체를 순회하면서 노드 객체의 상태를 변경할 때 주의해야 한다.
NodeList는 대부분 과거의 정적 상태를 유지하는 non-live 객체로 동작하지만 경우에 따라 live 객체로 동작할 때가 있다. querySelectorAll 메서드는 non-live NodeList 객체를 반환한다. NodeList 객체는 forEach, item, entries, keys, values 등의 프로토타입 메서드를 제공한다. 이런 점에서 유용하지만 childNodes 프로퍼티가 반환하는 NodeList 객체는 live 객체라는 문제점이 있다.
이러한 특징때문에 이 객체들의 상태를 변경할 때 예상과 다르게 동작할 수 있어 주의해야 한다. 따라서 배열로 변환하여 부작용을 줄이고 배열의 유용한 고차 함수를 사용하는 것이 좋다.
🍎 CSS 선택자(CSS selector)
스타일을 적용하고자 하는 HTML 요소를 특정할 때 사용하는 문법이다.
CSS 선택자 문법을 사용하는 querySelector, querySelectorAll 메서드는 getElementById, getElementsBy~ 메서드보다 다소 느린 것으로 알려져 있다. 하지만 CSS 선택자 문법을 사용하여 좀 더 구체적인 조건으로, 일관된 방식으로 요소 노드를 취득할 수 있다는 장점이 있다. 따라서 id 어트리뷰트가 있는 요소 노드를 취득할 때는 getElementById, 나머지 경우에는 querySelector, querySelectorAll 메서드를 사용하는 것을 권장한다.
노드 탐색
DOM 트리 상의 노드를 탐색할 수 있도록 Node, Element 인터페이스는 트리 탐색 프로퍼티를 제공한다.
노드 탐색 프로퍼티는 모두 getter만 존재하는 읽기 전용 접근자 프로퍼티다. 단, 값을 할당해도 에러 없이 무시된다.
노드를 탐색할 때는 공백 텍스트 노드에 주의해야 한다.
🍎 공백 텍스트 노드
HTML 요소 사이의 스페이스, 탭, 줄바꿈(개행) 등의 공백 문자는 텍스트 노드를 생성하는데, 이를 공백 텍스트 노드라 한다.
인위적으로 HTML 문서의 공백 문자를 제거하면 공백 텍스트 노드를 생성하지 않지만 가독성이 좋지 않으므로 권장하지 않는다.
자식 노드를 탐색하기 위해서는 다음과 같은 노드 탐색 프로퍼티를 사용한다.
프로퍼티 | 설명 |
Node.prototype.childNodes | 자식 노드를 모두 탐색하여 NodeList를 반환한다. 요소 노드뿐만 아니라 텍스트 노드도 포함된다. |
Node.prototype.firstChild | 첫 번째 자식 노드를 반환한다. 요소 노드뿐만 아니라 텍스트 노드도 포함된다. |
Node.prototype.lastChild | 마지막 자식 노드를 반환한다. 요소 노드뿐만 아니라 텍스트 노드도 포함된다. |
Element.prototype.children | 자식 노드 중 요소 노드만 모두 탐색하여 HTMLCollection에 담아 반환한다. 텍스트 노드는 포함되지 않는다. |
Element.prototype.firstElementChild | 자식 노드 중 첫번째 요소 노드를 반환한다. |
Element.prototype.lastElementChild | 자식 노드 중 마지막 요소 노드를 반환한다. |
자식 노드가 존재하는지 확인하려면 다음과 같은 프로퍼티를 사용한다. children.length로 요소 노드의 개수를 이용하여 검증하기도 한다.
프로퍼티 | 설명 |
Node.prototype.hasChildNodes | 자식 노드 존재 여부를 boolean 값으로 반환한다. 텍스트 노드를 포함한다. |
Element.prototype.childElementCount | 자식 노드 중 요소 노드의 개수를 반환한다. |
부모 또는 형제 노드를 탐색하려면 다음과 같은 노드 탐색 프로퍼티를 사용한다.
프로퍼티 | 설명 |
Node.prototype.parentNode | 부모 노드를 반환한다. 텍스트 노드는 리프 노드이므로 부모 노드가 텍스트 노드인 경우는 없다. |
Node.prototype.previousSibling | 형제 노드 중에서 자신의 이전 형제 노드를 반환한다. 요소 노드뿐만 아니라 텍스트 노드도 포함된다. |
Node.prototype.nextSibling | 형제 노드 중에서 자신의 다음 형제 노드를 반환한다. 요소 노드뿐만 아니라 텍스트 노드도 포함된다. |
Element.prototype.previousElementSibling | 형제 요소 노드 중에서 자신의 이전 형제 요소 노드를 반환한다. |
Element.prototype.nextElementSibling | 형제 요소 노드 중에서 자신의 다음 형제 요소 노드를 반환한다. |
노드 정보 취득
노드 객체 정보를 취득하려면 다음과 같은 노드 정보 프로퍼티를 사용한다.
프로퍼티 | 설명 |
Node.prototype.nodeType | 노드 타입을 나타내는 상수를 반환한다. - Node.ELEMENT_NODE === 1 - Node.TEXT_NODE === 3 - Node.DOCUMENT_NODE === 9 |
Node.prototype.nodeName | 노드의 이름을 문자열로 반환한다. 요소 노든느 태그 이름을 대문자 문자열로 반환하고, 텍스트 노드는 '#test', 문서 노드는 '#document'를 반환한다. |
요소 노드의 텍스트 조작
nodeValue와 textContent 프로퍼티는 모두 setter와 getter가 존재하는 접근자 프로퍼티다.
프로퍼티 | 설명 |
Node.prototype.nodeValue | 참조하면 노드 객체의 값을 반환한다. 노드 객체의 값이란 텍스트 노드의 텍스트를 말한다. 따라서 텍스트 노드가 아닌 노드의 nodeValue를 참조하면 null을 반환한다. 값을 할당하면 텍스트 노드의 값인 텍스트를 변경할 수 있다. |
Node.prototype.textContent | 요소 노드의 텍스트와 모든 자손 노드의 텍스트를 모두 취득하거나 변경한다. 참조하면 요소 노드의 콘텐츠 영역 내의 텍스트를 모두 반환한다. 이때 HTML 마크업은 무시된다. 문자열을 할당하면 요소 노드의 모든 자식 노드가 제거되고 할당한 문자열이 텍스트로 추가된다. HTML 마크업은 파싱되지 않고 문자열 그대로 인식된다. |
일반적으로 nodeValue 보다는 textContent 프로퍼티를 사용하는 편이 코드가 더 간단하다.
textContent와 비슷한 역할인 innerText 프로퍼티가 있지만, innerText는 CSS에 의해 비표시(visibility: hidden;)되는 요소 노드의 텍스트를 반환하지 않는 등 CSS에 의존적이고 CSS를 고려해야 하므로 textContent보다 느리다.
DOM 조작
DOM 조작은 새로운 노드를 생성하여 DOM에 추가하거나 기존 노드를 삭제 또는 교체하는 것을 말한다.
DOM을 조작하면 리플로우와 리페인트가 발생하므로 성능에 악영향을 준다.
DOM 조작에는 프로퍼티를 활용한 방법과 메서드를 활용하는 방법이 있다.
프로퍼티/메서드 | 설명 |
Element.prototype.innerHTML | setter와 getter 모두 존재하는 접근자 프로퍼티로, 요소 노드의 HTML 마크업을 취득하거나 변경한다. 참조하면 요소 노드의 콘텐츠 영역 내에 포함된 모든 HTML 마크업을 문자열로 반환한다. 문자열을 할당하면 요소 노드의 모든 자식 노드가 제거되고 할당한 문자열에 포함된 HTML 마크업이 파싱되어 요소 노드의 자식 노드로 DOM에 반영된다. HTML 마크업 문자열로 간단히 DOM 조작이 가능하다. XSS 공격에 취약하다. 변경이 되지 않아도 되는 자식 노드까지 모두 제거되어 비효율적이다. 새로운 요소를 삽입할 때 삽입될 위치를 지정할 수 없다. |
Element.prototype.insertAdjacentHTML(position, DOMString) | 기존 요소를 제거하지 않으면서 위치를 지정해 새로운 요소를 삽입한다. position으로 지정할 수 있는 문자열은 'beforebegin', 'afterbegin', 'beforeend', 'afterend'가 있다. 새롭게 삽입될 요소만을 파싱하여 자식 요소로 추가하므로 innerHTML 프로퍼티보다 효율적이고 빠르다. XSS 공격에 취약하다. |
Document.prototype.createElement(tagName) | 인자로 태그 이름을 나타내는 문자열을 전달하고 요소 노드를 생성하여 반환한다. 요소 노드를 생성할 뿐 DOM에 추가하지는 않는다. 생성한 요소 노드는 아무 자식 노드도 갖지 않는다. |
Document.prototype.createTextNode(text) | 인자로 텍스트 노드 값으로 사용할 문자열을 전달하고 텍스트 노드를 생성하여 반환한다. 테스트 노드를 생성할 뿐 요소 노드에 추가하지 않는다. |
Document.prototype.createDocumentFragment | 비어 있는 DocumentFragment 노드를 생성하여 반환한다. |
Node.prototype.appendChild(childNode) | 인자로 전달한 노드를 마지막 자식 노드로 추가한다. |
Node.prototype.insertBefore(newNode, childNode) | 첫 번째 인자로 전달받은 노드를 두 번째 인자로 전달받은 노드 앞에 삽입한다. 두번째 인자로 전달받은 노드는 반드시 insertBefore 메서드를 호출한 노드의 자식 노드여야 한다. 그렇지 않으면 DOMException 에러가 발생한다. 두번째 인자가 null일 경우 appendChild 메서드처럼 동작한다. |
Node.prototype.cloneNode([deep: true | false]) | 노드의 사본을 생성하여 반환한다. 인자로 true를 전달하면 깊은 복사하여 모든 자손 노드가 포함된 사본을 생성하고 false를 전달하거나 생략하면 얕은 복사하여 노드 자신만의 사본을 생성한다. |
Node.prototype.replaceChild(newChild, oldChild) | 자신을 호출한 노드의 자식 노드를 다른 노드로 교체한다. oldChild 노드는 DOM에서 제거된다. |
Node.prototype.removeChild(child) | 인자로 전달한 노드를 DOM에서 삭제한다. 인자로 전달한 노드는 removeChild 메서드를 호출한 노드의 자식 노드여야 한다. 그렇지 않으면 DOMException 에러가 발생한다. |
🍎 DocumentFragment 노드
노드 객체의 일종으로, 부모 노드가 없어서 기존 DOM 과는 별도로 존재한다.
자식 노드들의 부모 노드로서 별도의 서브 DOM을 구성하여 기존 DOM에 추가하기 위한 용도로 사용한다.
DocumentFragment 노드에 자식 노드를 추가해도 기존 DOM에는 어떠한 변경도 발생하지 않는다. 또한 DocumentFragment 노드를 DOM에 추가하면 자신은 제거되고 자식 노드만 DOM에 추가된다.
어트리뷰트
HTML 문서의 구성 요소인 HTML 요소는 여러 개의 어트리뷰트(attribute)를 가질 수 있다.
글로벌 어트리뷰트와 이벤트 핸들러 어트리뷰트는 모든 HTML 요소에서 공통적으로 사용할 수 있다. 특정 요소에만 한정적으로 사용 가능한 어트리뷰트도 있다.
HTML 요소의 어트리뷰트는 파싱될 때 어트리뷰트 노드로 변환되어 요소 노드와 연결된다. 어트리뷰트당 하나의 어트리뷰트 노드가 생성된다.
모든 어트리뷰트 노드의 참조는 유사 배열 객체이자 이터러블인 NamedNodeMap 객체에 담겨서 요소 노드의 attributes 프로퍼티에 저장된다. Elemebt.prototype.attributes 프로퍼티는 getter만 존재하는 읽기 전용 접근자 프로퍼티이며, 요소 노드의 모든 어트리뷰트 노드의 참조가 담긴 NamedNodeMap 객체를 반환한다.
어트리뷰트를 편하게 참조, 변경 및 삭제하기 위해 메서드를 사용한다.
메서드 | 설명 |
Element.prototype.getAttribute(attributeName) | 인자로 어트리뷰트 이름을 전달하면 어트리뷰트 값을 반환한다. |
Element.prototype.setAttribute(attributeName, attributeValue) | 인자로 어트리뷰트 이름과 값을 전달하면 어트리뷰트 값을 변경한다. |
Element.prototype.hasAttribute(attributeName) | 인자로 전달한 특정 HTML 어트리뷰트의 존재 여부를 나타내는 boolean 값을 반환한다. |
Element.prototype.removeAttribute(attributeName) | 인자로 전달한 특정 HTML 어트리뷰트를 삭제한다. |
🍎 HTML 어트리뷰트와 DOM 프로퍼티
요소 노드 객체에는 HTML 어트리뷰트에 대응하는 프로퍼티(DOM 프로퍼티)가 존재한다. 요소 노드는 상태를 가지고 있다. 요소 노드의 초기 상태는 어트리뷰트 노드가 관리하며, 최신 상태는 DOM 프로퍼티가 관리한다.
HTML 어트리뷰트의 역할은 HTML 요소의 초기 상태를 지정하는 것이다. 이 초기 상태는 어트리뷰트 노드에서 관리한다. 이 값은 사용자의 입력에 의해 상태가 변경되어도 변하지 않고 초기 상태를 그대로 유지한다. 어트리뷰트 노드가 관리하는 초기 상태 값을 취득 또는 변경하려면 getAttribute/setAttribute 메서드를 사용한다.
DOM 프로퍼티는 setter와 getter 모두 존재하는 접근자 프로퍼티다. DOM 프로퍼티는 HTML 어트리뷰트 값을 초기값으로 가지고 있다. 사용자의 입력에 의해 상태가 변경되면 DOM 프로퍼티가 변경되어 최신 상태를 유지한다. 이때 HTML 어트리뷰트 값에는 어떠한 영향도 주지 않는다. 단, 사용자 입력에 의한 상태 변화와 관계 있는 DOM 프로퍼티만 최신 상태 값을 관리한다. 그 외는 항상 HTML 어트리뷰트와 동일한 값으로 연동된다.
항상 HTML 어트리뷰트 이름과 DOM 프로퍼티 이름이 동일하거나, 항상 1:1로 대응하지는 않는다.
data 어트리뷰트와 dataset 프로퍼티를 사용하면 HTML 요소에 정의한 사용자 정의 어트리뷰트와 자바스크립트 간에 데이터를 교환할 수 있다. data 어트리뷰트의 값은 HTMLElement.dataset 프로퍼티로 취득하거나 변경할 수 있다.
스타일
인라인으로 스타일 조작을 할 수 있다. HTMLElement.prototype.style 프로퍼티는 setter와 getter 모두 존재하는 접근자 프로퍼티로서 요소 노드의 인라인 스타일을 취득, 추가 및 변경한다.
style 프로퍼티를 참조하면 CSSStyleDeclaration 타입의 객체를 반환한다. 이 객체의 프로퍼티에 값을 할당하면 해당 프로퍼티가 인라인 스타일로 HTML 요소에 추가되거나 변경된다. 이때 단위 지정이 필요한 CSS 프로퍼티는 단위를 생략하면 적용되지 않는다.
클래스를 조작하여 스타일을 변경할 수도 있다. 클래스 선택자로 CSS class를 미리 정의한 다음, HTML 요소의 class 어트리뷰트 값을 변경하여 HTML 요소의 스타일을 변경한다. 관련 프로퍼티 및 메서드는 다음과 같다.
프로퍼티/메서드 | 설명 |
Element.prototype.className | setter와 getter 모두 존재하는 접근자 프로퍼티로서 HTML 요소의 class 어트리뷰트 값을 취득하거나 변경한다. 참조하면 class 어트리뷰트 값을 문자열로 반환하고, 문자열을 할당하면 class 어트리뷰트 값을 변경한다. 공백으로 구분된 여러 개의 클래스를 반환하는 경우 다루기가 불편하다. |
Element.prototype.classList | class 어트리뷰트의 정보를 담은 DOMTokenList 객체를 반환한다. |
DOMTokenList.add(...className) | 인자로 전달한 1개 이상의 문자열을 class 어트리뷰트 값으로 추가한다. |
DOMTokenList.remove(...className) | 인자로 전달한 1개 이상의 문자열과 일치하는 클래스를 class 어트리뷰트에서 삭제한다. 존재하지 않으면 에러 없이 무시된다. |
DOMTokenList.item(index) | 인자로 전달한 index에 해당하는 클래스를 class 어트리뷰트에서 반환한다. |
DOMTokenList.contains(className) | 인자로 전달한 문자열과 일치하는 클래스가 class 어트리뷰트에 포함되어 있는지 확인한다. |
DOMTokenList.replace(oldClassName, newClassName) | class 어트리뷰트에서 첫 번째 인자로 전달한 문자열을 두 번째 인자로 전달한 문자열로 변경한다. |
DOMTokenList.toggle(className[, force]) | class 어트리뷰트에 인자로 전달한 문자열과 일치하는 클래스가 존재하면 제거하고, 존재하지 않으면 추가한다. 두번째 인자로 boolean 값으로 평가되는 조건식을 전달할 수 있다. 조건식의 평가 결과가 true면 추가, false면 제거한다. |
window.getComputedStyle(element[, pseudo]) | style 프로퍼티는 인라인 스타일만 반환하기때문에 HTML 요소에 적용되어 있는 모든 CSS 스타일을 참조해야 할 경우 사용한다. 첫번째 인자로 전달한 요소 노드에 적용되어 평가된 스타일을 CSSStyleDeclaration 객체에 담아 반환한다. 두번째 인자로 :after, :before과 같은 의사 요소를 지정하는 문자열을 전달할 수 있다. |
🍎 DOMTokenList 객체
class 어트리뷰트의 정보를 나타내는 컬렉션 객체로서 유사 배열 객체이면서 이터러블이다.
위에서 설명한 메서드 외에도 forEach, entries, keys, values, supports 등의 유용한 메서드를 제공한다.
DOM 표준
HTMl과 DOM 표준은 W3C(World Wide Web Consortium)과 WHATWG(Web Hypertext Application Technology Working Group)이 협력하며 공통 표준을 만들어 왔다.
2018년 4월부터 구글, 애플, MS, 모질라로 구성된 4개의 주류 브라우저 벤더사가 주도하는 WHATWG가 단일 표준을 내놓기로 합의되었다.
DOM은 현재 4개의 버전이 있다.
'독서 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
모던 자바스크립트 Deep Dive (40장 ~ 41장) (0) | 2023.05.31 |
---|---|
모던 자바스크립트 Deep Dive (34장 ~ 37장) (0) | 2023.05.19 |
모던 자바스크립트 Deep Dive (30장 ~ 33장) (0) | 2023.05.14 |
모던 자바스크립트 Deep Dive (27장 ~ 29장) (0) | 2023.05.07 |
모던 자바스크립트 Deep Dive (25장 ~ 26장) (0) | 2023.04.28 |