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 태그를 위치시키는 방법