본문 바로가기

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로 작성된 문서를 파싱하여 브라우저에서 시각적으로 출력하는 것

브라우저 렌더링 과정 개요


  1. 브라우저가 HTML, CSS, JS, 이미지, 폰트 파일 등 렌더링에 필요한 요청 -> 서버로 응답 받음
  2. 브라우저의 렌더링 엔진이 서버로 부터 응답된 HTML, CSS를 파싱 -> DOM, CSSOM을 생성 -> 결합하여 렌더 트리 생성
  3. 브라우저의 JS 엔진이 서버로 부터 응답된 JS를 파싱 -> AST(Abstract Syntax Tree) 생성 -> 바이트코드로 변환 -> 실행 (JS가 DOM API로 DOM, CSSOM 변경) -> 변경된 DOM, CSSOM이 렌더 트리로 결합
  4. 렌더 트리 기반으로 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 문서를 파싱한 결과물
    1. HTML 요청을 받은 서버가 해당 HTML 파일을 읽어 바이트(2진수)로 메모리에 저장하고, 해당 바이트를 Network로 전송하여 응답함
    2. 브라우저는 서버가 응답한 HTML 문서를 바이트 형태로 응답 받음
    3. 바이트 형태의 HTML 문서를 읽고, 응답 헤더에 있는 meta태그 charset 어트리뷰트 인코딩 방식으로 바이트를 문자열로 변환함
    4. 문자열을 읽어 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해
    5. 각 토큰들을 객체로 변환하여 노드을 생성 (노드는 DOM을 구성하는 기본 요소)
    6. 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 -> 바이트 코드

리플로우, 리페인트


  • 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 파일 파싱과 실행