JavaScript
20210715 JavaSciprt DeepDive 13 : Set, Map, 브라우저 렌더링 과정
JavaScript Deep Dive 13
📄 용어 및 중요사항 정리
Set
Set
- 중복되지 않는 유일한 값들의 집합 객체
- 수학적 집합을 구현하기 위한 자료구조 임
- JS의 모든 값을 요소로 지정할 수 있음
- set 객체는 이터러블 임
- for...of, 스프레드 문법, 배열 디스트럭처링 가능
- 배열 vs Set 객체
구분 | 배열 | Set 객체 |
---|---|---|
동일한 값을 중복 | O | X |
요소 순서 의미 | O | X |
인덱스로 요소 접근 | O | X |
Set 객체 생성
new Set(이터러블)
- Set 생성자 함수로 생성
- 이터러블을 인수로 전달받아 Set 객체 생성
- 인수가 없으면, 빈 Set 객체 생성됨
- 이터러블의 중복된 값은 Set 객체에 요소로 저장 되지 않음
Set 프로퍼티, 메서드
Set.prototype.size 프로퍼티
- Set 객체의 요소 개수 확인
- setter 함수 없이, getter만 존재하는 접근자 프로퍼티
- size 프로퍼티에 값을 할당하여 변경 불가함
Set.prototype.add(요소)
- 새로운 요소가 추가된 Set 객체 반환
- 메서드 체이닝으로 연속해서 호출 가능함
- 중복된 요소 추가의 경우에는 무시되고 에러는 발생하지 않음
- NaN의 중복도 평가 함
- 일반 일치 비교 연산자에서는 NaN은 다르다고 평가함
const set = new Set();
set.add(1).add("a").add(null);
console.log(set); // Set(3) {1, 'a', null}
Set.prototype.has(요소값)
- 특정 요소의 존재 여부를 나타내는 불리언 값을 반환
Set.prototype.delete(요소값)
- 특정 요소를 삭제하고, 삭제 성공 여부를 나타내는 불리언 값 반환
- 연속적으로 호출 불가함
Set.prototype.clear()
- 모든 요소를 일괄 삭제하고 언제나 undefined를 반환
Set.prototype.forEach(callback, this)
callback(value, value, set)
- 첫번째 인수, 두번째 인수: 순회중 인 요소로 같음(단지 같은 이유에 의미는 없음)
- 세번째 인수 : Set 객체 자체
- set 객체는 인덱스가 없기 때문에, 두번째 인수에 인덱스는 없음
- 순회 순서는 요소가 추가된 순서를 따름
집합 연산 프로토타입 메서드 구현
교집합
- 집합 두개의 교집합을 집합으로 만듦
- for ... of로 구현한 교집합 메서드
- filter로 구현한 교집합 메서드
// for...of 을 통한 비교
Set.prototype.intersection = function (set) {
// 교집합을 담을 Set
const result = new Set();
// 비교할 Set 객체를 순회하면서, 기존 Set에 값이 있는지 확인하고 교집합 Set에 넣음
for (const value of set) {
if (this.has(value)) result.add(value);
}
// 교집합 Set 반환
return result;
};
// filter 을 통한 비교
Set.prototype.intersection = function (set) {
return new Set([...this].filter((v) => set.has(v)));
};
합집합
- 집합 두 개를 합침
- for...of 방식
- 스프레드 문법 방식
// for ... of 방식
Set.prototype.union = function (set) {
const result = new Set(this);
for (const value of set) {
result.add(value);
}
return result;
};
// 스프레드 문법 방식
Set.prototype.union = function (set) {
return new Set([...this, ...set]);
};
차집합
- A, B중 A에만 있는 요소들의 집합
Set.prototype.difference = function (set) {
const result = new Set(this);
for (const value of set) {
result.delete(value);
}
return result;
};
Set.prototype.difference = function (set) {
return new Set([...this].filter((v) => !set.has(v)));
};
부분 집합과 상위 집합
- 상위 집합과 부분 집합 관계 체크
- for...of 방식 비교
- every 방식 비교
// for...of 방식의 this 상위 집합 확인
Set.prototype.isSuperset = function (subset) {
for (const value of subset) {
if (!this.has(value)) return false;
}
return true;
};
// every 방식의 this 상위 집합 확인
Set.prototype.isSuperset = function (subset) {
const supersetArr = [...this];
return [...subset].every((v) => supersetArr.includes(v));
};
Map
Map
- 키와 값의 쌍으로 이루어진 컬렉션(자료구조)
- 객체와 유사하지만 다름
- 객체는 문자열, 심벌만 키값으로 사용가능 함
- Map은 JS의 모든 값을 키로 활용 할수 있음
- Map 객체는 이터러블 임
- for...of, 스프레드 문법, 디스트럭처링 할당 가능
- 순서에 의미는 없지만 순회하는 순서는 요소가 추가된 순서를 따름
- Map vs Object
구분 | 객체 | Map 객체 |
---|---|---|
키로 사용할 수 있는 값 | 문자열 또는 심벌 값 | 객체를 포함한 모든 값 |
이터러블 | X | O |
요소 개수 확인 | Object.keys(obj).length | map.size |
Map 객체 생성
new Map(이터러블)
- Map 생성자 함수로 생성
- 인수를 전달하지 않으면 빈 Map 객체 생성
- 이터러블 -> key-value 쌍으로 이루어진 요소로 구성되어야 함
- 이터러블에 중복된 키를 갖는 요소 존재시 값을 덮어 씀 (중복키 존재X)
const map = new Map([
["key1", "value1"],
["key2", "value2"],
["key2", "value3"],
]);
console.log(map); // Map(2) {"key1" => "value1", "key2" => "value3"}
Map 프로토타입 메서드, 프로퍼티
Map.prototype.size 프로퍼티
- Map 객체의 요소 개수 확인
- setter는 없어서, 프로퍼티에 값 할당하여 변경 못함
- Set 과 마찬가지로 값 할당시 에러없이 무시됨
Map.prototype.set(key, value)
- Map 객체에 요소를 추가할 때 사용
- 메서드 체이닝으로 연속 호출 가능
- 중복 키를 갖는 요소 추가시 기존값 덮어 씀
- NaN도 중복으로 평가
Map.prototype.get(key)
- 요소를 취득할 때 사용
- 인수로 전달한 key에 맞는 값(value)를 반환
- key가 존재하지 않는 경우 undefined 반환
Map.prototype.has(key)
- 요소 존재 여부 확인시 사용
- 요소의 존재를 나타내는 불리언 값 반환
Map.prototype.delete(key)
- 요소를 삭제할 때 사용
- 삭제 성공 여부에 따른 불리언 값을 반환
- 존재하지 않는 키로 Map객체 요소 삭제시 에러없이 무시됨
- 연속적으로 호출 불가함
Map.prototype.clear()
- 요소 일괄 삭제시 사용
- 언제나 undefined 반환
Map.prototype.forEach(Callback, this)
callback(value, key, Map)
- 요소 순회시 사용
이터러블이면서 동시에 이터레이터인 객체를 반환하는 메서드
Map.prototype.keys()
- 요소키를 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체 반환
Map.prototype.values()
- 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체 반환
Map.prototype.entries()
- 요소키와 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체 반환
const park = { name: "Park" };
const back = { name: "Back" };
const map = new Map([
[park, "developer"],
[back, "designer"],
]);
console.log(map.keys());
console.log(map.values());
console.log(map.entries());
브라우저 렌더링 과정
파싱 (parsing)
- 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위한 분석 과정
- 텍스트 문서의 문자열을 토큰으로 분해
- 토큰에 문법적 의미와 구조를 반영하여 트리구조의 자료구조인 파스 트리를 생성
- 파싱 완료 -> 파스 트리 기반 중간 언어인 바이트코드 생성 실행
- 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위한 분석 과정
렌더링 (rendering)
- HTNL, CSS, JS로 작성된 문서를 파싱하여 브라우저에서 시각적으로 출력하는 것
브라우저 렌더링 과정 개요
- 브라우저가 HTML, CSS, JS, 이미지, 폰트 파일 등 렌더링에 필요한 요청 -> 서버로 응답 받음
- 브라우저의 렌더링 엔진이 서버로 부터 응답된 HTML, CSS를 파싱 -> DOM, CSSOM을 생성 -> 결합하여 렌더 트리 생성
- 브라우저의 JS 엔진이 서버로 부터 응답된 JS를 파싱 -> AST(Abstract Syntax Tree) 생성 -> 바이트코드로 변환 -> 실행 (JS가 DOM API로 DOM, CSSOM 변경) -> 변경된 DOM, CSSOM이 렌더 트리로 결합
- 렌더 트리 기반으로 HTML 요소의 레이아웃(위치, 크기) 계산하여 브라우저 화면에 HTML 요소 페인팅
요청과 응답
- 브라우저의 주소창 : 서버에 요청 전송용
- 주소창에 URL을 입력 엔터 -> URL의 호스트 이름 -> (DNS가 IP주소로 변환함) -> IP주소를 갖는 서버에게 요청 전송
루트 요청
: 스킴(scheme)과 호스트(host)만으로 구성된 URI에 의한 요청이 서버로 전송- 요청 내용은 없지만 암묵적으로 서버는 응답으로 index.html을 보내도록 되어 있음
- index.html에 있는 link태그(CSS파일), img태그(이미지 파일), script태그(JS파일) 등을 만나면 HTML 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청
정적 파일 요청
: 다른 정적 파일 필요시 URI + Path를 기술하여 서버에 요청동적 파일 요청
: JS를 통해 동적으로 서버에 정적/동적 데이터 요청 가능- 요청과 응답은 개발자 도구 Network 패널에서 확인 가능
HTTP 1.1과 HTTP 2.0
HTTP
: 웹에서 브라우저와 서버가 통신하기 위한 프로토콜(규약)HTTP/1.1
:- 커넥션당 하나의 요청과 응답만 처리 (여러개 요청, 응답을 한번에 전송 불가)
- index.html 응답 후 CSS, 이미지, JS파일 차례대로 요청, 응답
- 리소스의 동시 전송이 불가능한 구조 -> 요청 개수에 비례한 응답 시간 증가
- 커넥션당 하나의 요청과 응답만 처리 (여러개 요청, 응답을 한번에 전송 불가)
HTTP/2.0
:- 커넥션당 여러개의 요청과 응답 가능 (다중 요청, 응답이 가능)
- index.html 응답 후 CSS, 이미지, JS 파일 한번에 동시 요청, 응답
- HTTP/1.1에 비해 50% 정도 빠름
- 커넥션당 여러개의 요청과 응답 가능 (다중 요청, 응답이 가능)
HTLM 파싱과 DOM 생성
- DOM은 HTML 문서를 파싱한 결과물
- HTML 요청을 받은 서버가 해당 HTML 파일을 읽어 바이트(2진수)로 메모리에 저장하고, 해당 바이트를 Network로 전송하여 응답함
- 브라우저는 서버가 응답한 HTML 문서를 바이트 형태로 응답 받음
- 바이트 형태의 HTML 문서를 읽고, 응답 헤더에 있는 meta태그 charset 어트리뷰트 인코딩 방식으로 바이트를 문자열로 변환함
- 문자열을 읽어 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해
- 각 토큰들을 객체로 변환하여 노드을 생성 (노드는 DOM을 구성하는 기본 요소)
- HTML 요소들의 중첩 관계를 반영하여 모든 노드들을 트리 자료구조(DOM)로 구성함
CSS 파싱과 CSSOM 생성
렌더링 엔진이 순차적으로 HTML을 파싱하면서 DOM 생성중 link, style 태그를 만나면 DOM 생성을 일시 중단함
- link 태그의 href 어트리뷰트에 지정된 css 파일을 서버에 요청
- 브라우저는 바이트로 받고 -> 문자열 -> 토큰 -> 노드 -> (상속 관계 반영) -> CSSOM 을 거쳐 CSSOM을 생성함
- HTML 파싱중 중단된 지점부터 다시 파싱하면서 DOM 생성
렌더링 엔진
: 서버로 부터 응답된 HTML, CSS를 파싱하여 DOM, CSSOM를 생성함- DOM, CSSOM은 렌더 트리로 결합됨
렌더 트리
: 렌더링을 위한 트리 구조의 자료 구조- 비표시 노드들은 포함되지 않기 때문에 렌더링 되는 노드만으로 구성됨
레이아웃
: 렌더 트리를 가지고 HTML 요소의 위치 크기를 계산하는 작업페인팅
: 계산된 레이아웃 대로 브라우저 화면에 픽셀을 렌더링 하는 작업리렌더링
: 레이아웃 계산과 페인팅을 다시 실행하는 것- 리렌더링은 성능에 악영향을 줌, 리렌더링 제어가 필요함
- 다시 실행 되는 경우
- 자바스크립트에 의한 노드 추가 또는 삭제
- 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
- HTML 요소의 레이아웃에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경
JS 파싱과 실행
- JS 코드에서 DOM API 사용시 이미 생성된 DOM을 동적으로 조작 가능
- 렌더링 엔진이 HTML 파싱하며 DOM 생성 중에 script 태그를 만나면 DOM 생성 일시 중단
- script src 어트리뷰트에 정의된 자바스크립트 파일을 서버에 요청
- 자바스크립트 엔진이 문자열인 JS 소스코드 -(토크나이징)-> 토큰 -(파싱)> AST -(바이트 코드 생성)-> 바이트 코드 -> 실행
토크나이징(tokenizing)
: 단순한 문자열인 자바스크립트 소스 코드를 어위 분석하여 문법적 의미의 토큰들로 분해- 문자열 JS 코드 -> 토큰
파싱
: 토큰들의 집합을 구문 분석하여 AST를 생성- 토큰 -> AST
AST
: 인터프리터나 컴파일러가 사용 또는 TypeScript, Babel, Prettier 같은 트랜스파일러를 구현할 수 있음
바이트 코드 생성
:- AST는 인터프리터가 실행할수 있는 중간 코드 바이트코드로 변환되고 인터프리터로 실행됨
- AST -> 바이트 코드
- AST는 인터프리터가 실행할수 있는 중간 코드 바이트코드로 변환되고 인터프리터로 실행됨
리플로우, 리페인트
- JS 코드에서 DOM API로 인해 DOM, CSSOM이 변경 -> 렌더 트리 결합 -> 레이아웃 -> 페인트
리플로우
: 레이아웃 계산을 다시 하는 것을 말함- 노드추가/삭제, 요소크기/위치 변경, 윈도우 리사이징 등의 변경 -> 리플로우 발생
리페인트
: 재결합된 렌더 트리를 기반으로 다시 페이트 하는 것- 리플로우, 리페인트 순차적 동시 실행이 아니라, 레이아웃에 영향 없으면 리페인트만 실행됨
직렬적, 병렬적 파싱
직렬적 파싱
: 렌더링엔진, JS엔진이 직렬적(동기적)으로 위에서 아래로 파싱하는 것- script 태그의 위치를 주의 해야함 -> DOM API를 사용하는데, DOM의 생성이 없으면 문제 발생함
- 블록킹 : HTML 파싱중 중단 됨
블록킹 해결 방식
- 방법01 : body 요소의 가장 아래에 script 태그를 위치시키는 방법
- DOM 조작시 에러 방지, HTML 먼저 파싱 실행되어 페이지 로딩시간 단축
- 방법02 : async, defer 어트리뷰트 사용 (인라인 JS는 불가, 외부 파일 scr로 연결시만 가능) -> HTNL, JS파일 로드가 비동기적으로 병렬 진행
async 어트리뷰트
: HTML 파싱과 JS파일 로드를 병렬적(비동기적)으로 진행- JS 로드 완료시 JS 파일 실행 -> 로드만 되면 순서 없이 실행 됨 (순서 보장X)
defer 어트리뷰트
: HTML 파싱과 JS파일 로드를 병렬적(비동기적)으로 진행- HTML 파싱 완료된 직후 JS 파일 파싱과 실행
- 방법01 : body 요소의 가장 아래에 script 태그를 위치시키는 방법