📚프론트엔드 기술 면접 대비 스터디
1주차
- 브라우저 렌더링 과정을 설명해주세요
- 호이스팅에 대해서 설명해보세요
- 클로저는 무엇인가요? 원리와 왜 사용하는지 설명해주세요
2주차- 스코프란 무엇인가요?
- 실행 컨텍스트에 대해서 설명해주세요
- Async, Await와 Promise의 차이는 무엇인가요 ?
3주차- 타입스크립트를 사용해본 적이 있나요? 어떤지 말씀해주세요
- Virtual DOM에 대해 아는 것을 말해주세요 (개념, 원리 등등)
- props와 state에 대해 설명해주세요
UI (사용자 인터페이스)
: 브라우저의 주소 표시줄, 이전/다음 버튼, 북마크 등과 같이 사용자가 직접 조작할 수 있는 영역브라우저 엔진 (Brower Engine)
: UI와 렌더링 엔진 사이의 동작을 제어렌더링 엔진 (Rendering Engine)
: 사용자가 요청한 내용에 대한 결과를 화면에 표시. 즉, HTML과 CSS을 파싱하여 화면에 표시통신 (Network)
: HTTP 요청과 같은 네트워크 호출에 사용자바스크립트 해석기 (JavaScript Interpreter)
: 자바스크립트 코드를 해석하고 실행 (JavaScript Engine 이라고도 함. ex) 크롬 V8엔진)UI 백엔드
: 브라우저의 select, input, modal 등과 같은 기본적인 위젯을 그리는 인터페이스자료 저장소 (Data Storage)
: Cookie, Local Storage 등과 같이 브라우저 메모리를 활용하여 데이터를 저장DOM
생성
렌더링 엔진은 HTML 문서를 파싱하여 HTML 문서에 있는 모든 것들로 DOM을 구성한다.
CSSOM
생성
렌더링 엔진은 DOM을 생성하다가 css 태그를 만나면 css를 파싱하고 CSSOM을 만든다. CSSOM은 DOM이 화면에 어떻게 표시될지를 알려주는 역할을 한다.
Render Tree
생성
DOM과 CSSOM을 결합하여 Render Tree를 생성한다. Render Tree는 화면에 표시되어야 할 모든 노드의 컨텐츠, 스타일 정도를 포함하는 트리이다.
Render Tree는 DOM 트리의 루트(html)에서부터 각 노드를 순회하면서 각각의 맞는 CSSOM을 찾아서 규칙을 적용하여 생성된다.
참고로 화면에 표시되지 않는 노드는 렌더 트리에 포함되지 않는다. (<head>, <title>, <script> 등)
❗주의
display: none
은 눈에 보이지도 않고 공간을 차지하지도 않기 때문에 렌더 트리에 포함되지 않는다.
visibility: hidden
은 눈에 보이지는 않지만 공간을 차지하므로 렌더 트리에 포함된다.
Layout
(Render Tree 배치)
기기의 뷰포트(viewport) 내에서 노드의 정확한 위치와 크기를 계산한다. css에서 상대적인 단위(%, em 등)를 사용한 부분은 뷰포트에 맞춰서 절대적인 px단위로 변환된다.
이렇게 레이아웃 과정에서 렌더링 엔진은 각 요소들이 어떻게 생겼고 어떻게 보여줘야 하는지 알게 된다.
Paint
(Render Tree 그리기)
렌더 트리를 순회하면서 페인트 함수를 호출해 노드를 화면에 실제 픽셀로 그려지도록 표현한다. 이렇게 하면 최초 렌더링이 완료된다.
① 렌더링 엔진은 HTML 문서를 파싱하고 DOM을 생성한다.
② css 태그를 만나면 css를 파싱하고 CSSOM을 만든다.
③ DOM과 CSSOM을 결합하여 Render Tree를 생성한다.
④ 뷰포트 내에서 노드의 정확한 위치와 크기를 계산하여 Render Tree를 배치한다.
⑤ Render Tree의 노드를 화면에 실제 픽셀로 그린다.
호이스팅(Hoisting)
은 변수 선언문이 마치 호이스팅 최상단에 끌어올려진 듯한 현상이다. 실제로 선언문이 있는 코드라인을 물리적으로 최상단으로 끌어올린 것이 아니라, 자바스크립트 엔진이 먼저 코드 전체를 스캔하면서 변수를 실행 컨텍스트에 미리 기록해두기 때문에 발생하는 것이다.
var
, let
, const
모두 호이스팅이 발생한다. 하지만 변수 생성 과정의 차이로 인해 값이 할당되기 전에 참조하면 서로 다른 결과를 도출한다.
변수의 생성 단계는 3가지로 나뉜다.
1. 선언 단계
2. 초기화 단계
3. 할당 단계
var
는 선언과 초기화가 동시에 진행된다. 이 때 값을 undefined로 초기화하기 때문에 값을 할당하기 전에 참조해도 오류가 나지 않고 undefined 값을 얻는다.
반면 let
과 const
는 선언 단계와 초기화 단계가 분리되어 진행된다. 호이스팅 되면서 선언단계가 이루어지지만, 초기화 단계는 실제 코드에 도달했을 때 진행된다. 따라서, 실제 코드에 도달하기 전에 먼저 참조를 하게 되면 ReferenceError가 발생한다.
이는 Temporary Dead Zone(TDZ)
때문이다. let과 const로 선언된 변수들은 실제 코드에 도달하기 전까지는 TDZ 영역에 존재하며, 접근할 수 없다. TDZ로 인해 잠재적인 버그를 줄일 수 있다.
* 변수 선언 코드에 도달하기 전까지의 영역을 변수에 접근할 수 없다는 의미로 Temporary Dead Zone(TDZ)이라고 한다.
함수 표현식(const func = () => {}
)은 함수를 변수에 담고 있기 때문에 변수 호이스팅과 동일하게 동작한다.
함수 선언문(function func() {}
)은 실행 컨텍스트에 함수 전체가 통채로 저장되기 때문에 함수 이름을 key로 하고 함수 자체를 value로 저장하여 완전하게 초기화된다. 따라서, 완전하게 저장된 상태에서 접근하는 것이기 때문에 변수 선언 이전에 변수에 접근하더라도 변수를 참조할 수 있다.
호이스팅은 변수 선언문이 마치 호이스팅 최상단에 끌어올려진 듯한 현상이다.
var로 선언된 변수는 선언과 초기화가 동시에 진행된다. 값을 할당하기 전에 참조해도 오류가 나지 않고 undefined 값을 얻는다.
let과 const로 선언된 변수는 선언과 초기화가 분리되어 진행된다. 실제 코드에 도달하기 전까지는 TDZ 영역에 존재하며, 접근할 수 없기 때문에 ReferenceError가 발생한다.
스코프
는 변수의 접근 가능 유효범위로 변수 이름, 함수 이름과 같은 식별자가 선언된 위치에 따라 다른 코드에서 참조 가능 여부가 결정되는 것이다.
전역적으로 선언된 변수는 전역 스코프
에, 지역적으로 선언된 변수는 지역 스코프
에 있다.
전역스코프와 지역스코프는 서로 계층적으로 연결되어 있는 데, 이것을 스코프 체인
이라 한다. 스코프 체인은 물리적으로 존재하며 자바스크립트 엔진은 이 스코프 체인을 통해 변수를 참조한다.
스코프 체인은 무조건 상위(지역->전역) 스코프로만 올라가면서 참조한다. 자신의 스코프에서 변수를 찾고 해당 변수가 없다면, 상위 스코프로 타고 타고 올라가면서 변수를 찾는다. 이 과정에서 변수를 찾으면 반환하고, 가장 상위인 전역 스코프까지 와서도 끝내 못 찾는다면 ReferenceError를 반환한다.
이렇게 계층적으로 연결되어 있는 스코프 체인 덕분에 하위 스코프에서 상위 스코프에 있는 변수를 참조할 수 있는 것이다.
*생명 주기를 마감한 상위 함수의 변수를 참조할 수 있는 이유 -> 클로저
스코프체인 참조 과정
1. fooColor를 출력하려할 때 bar 스코프에는 fooColor가 존재하지 않음.
2. 스코프 체인을 타고 foo 스코프로 이동하여 변수를 찾아내고 출력함.
3. 마찬가지로 globalColor를 출력하려할 때 bar 스코프에는 fooColor가 존재하지 않음
4. 스코프 체인을 타고 foo 스코프로 이동하여 변수를 찾음. 하지만, foo 스코프에도 존재하지 않음.
5. global 스코프로 이동하여 변수를 찾아내고 출력함.
스코프는 변수의 접근 가능 유효범위이며, 상위 스코프와 하위 스코프들이 계층적으로 연결되어 있는 것을 스코프 체인이라고 한다. 이 때문에 하위 스코프에서 상위 스코프에 있는 변수를 참조할 수 있는 것이다.
클로저
는 함수와 그 함수의 렉시컬 환경의 조합이다. 클로저의 핵심은 스코프를 이용해서, 변수의 접근 범위를 지정하는 것이다.
클로저를 사용하면 스코프에 데이터를 보존하기 때문에 외부 함수의 실행이 끝나더라도 외부 함수 스코프에서 선언된 변수에 접근이 가능하다.
또한, 정보의 접근을 제한할 수 있다.
클로저 함수를 변수에 할당하면 각자 독립적으로 값을 사용하고 보존할 수 있기 때문에 모듈화에 유리해진다.
클로저는 함수와 그 함수의 렉시컬 환경의 조합이다. 클로저의 핵심은 스코프를 이용해서, 변수의 접근 범위를 지정하는 것이다. 클로저를 사용하면 데이터 보존이 가능하고 정보의 접근을 제한할 수 있으며, 모듈화에 유리해진다.
실행 컨텍스트
는 코드를 실행하는 데 필요한 환경(조건이나 상태)을 제공하는 객체이며, 식별자를 더욱 효율적으로 결정하기 위한 수단이 된다.
실행 컨텍스트는 Variable Environment
, Lexical Environment
, ThisBinding
을 구성 요소로 가진다.
실행 컨텍스트는 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 객체를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행한다. 이렇게 해서 전체 코드의 환경과 순서를 보장할 수 있다. 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행한다.
실행 컨텍스트
는 코드를 실행하는 데 필요한 환경(조건이나 상태)을 제공하는 객체이며, 식별자를 더욱 효율적으로 결정하기 위한 수단이다. 실행 컨텍스트로 전체 코드의 환경과 순서를 보장할 수 있다.
async/await문을 사용하면 동기적 코드 흐름으로 개발이 가능하다.
promise는 계속해서 .then()
으로 체이닝하다보면 콜백 지옥마냥 코드가 복잡해질 수 있다. async/await문을 사용하면 promise에 비해 더욱 간결하고 직관적인 코드 작성이 가능해진다.
promise는 .catch()
로 에러 핸들링을 한다. 하지만 async/await문은 try~catch
문을 활용하여 에러 핸들링을 한다. try 내부에서 발생하는 모든 에러를 catch문에서 접근할 수 있기 때문에 async/await의 에러 처리가 훨씬 유리하고 에러 찾기도 쉽다.
타입스크립트는 타입을 명시적으로 지정한다는 것이 자바스크립트와의 가장 큰 차이점이었다. 이 점이 낯설고 문법적으로 까다롭다는 느낌을 받기도 했다. 하지만 그만큼 유연한 문법으로 인해 에러가 발생하던 자바스크립트의 단점을 극복할 수 있겠다는 생각이 들었다.
이렇게 타입을 명시적으로 지정함으로써, 컴파일 시점에 에러를 빨리 발견할 수 있고 메모리 절약을 할 수 있는 등 장점을 얻을 수 있다. 자바스크립트 상위확장버전인 만큼, 프론트 영역에서는 앞으로 더욱 필수적이고 활용도가 높아지지 않을까 라는 생각이 든다.
Virtual DOM
은 추상화된 DOM으로, DOM과 유사한 역할을 담당하는 객체이다.
DOM을 반복적으로 직접 조작하면 그 만큼 브라우저가 렌더링을 자주하게 되고, (렌더트리를 재생성하고 레이아웃을 만들고 페인팅을 하는 과정을 매번 반복) 그 만큼 PC 자원을 많이 소모하게 되는 문제가 발생한다. 이러한 문제를 해결하기 위해 Virtual DOM을 사용하는 것이다.
변경 사항이 발생하면 DOM에 직접 수정하지 않고 중간 단계로 Virtual DOM을 수정한다. 그 후 Virtual DOM의 변경된 내역을 한 번에 모으고 DOM과 Virtual DOM의 차이를 판단하여 변경된 부분만 찾아 실제 DOM을 변경한다. 따라서 변경이 발생할 때마다 매번 렌더링 하지 않고 렌더링은 한 번만 하게 된다. 이렇게 SPA에서 DOM 복잡도 증가에 따른 최적화 및 유지 보수가 더 어려워 지는 문제를 해결할 수 있다.
그런데 사실 이 과정들은 Virtual DOM 없이도 DOM fragment를 통해 이뤄질 수 있다. 변화가 있을 때 DOM fragment에 적용한 다음, 기존 DOM 에 던져주는 식으로 말이다.
그럼에도 불구하고 Virtual DOM을 사용하는 이유는 DOM fragment를 관리하는 과정을 수동으로 하나하나 작업 할 필요 없이 Virtual DOM이 이 과정들을 자동화하고 추상화해주기 때문이다. 즉, Virtual DOM이 자동으로 해주기 때문에 훨씬 편하게 관리할 수 있는 것이다.
Virtual DOM
은 추상화된 DOM으로 DOM을 직접 조작할 때마다 매번 렌더링되는 문제점을 해결하기 위해 사용된다. 변경사항이 발생하면 DOM이 아닌 Virtual DOM을 수정하고 Virtual DOM의 변경된 내역을 한 번에 모은다. 그 후 변경된 부분만 찾아 실제 DOM에 반영함으로써 렌더링은 한 번만 발생한다.
Props
는 컴포넌트에서 컴포넌트로 값을 전달해줄 때 사용한다. 단, 부모요소에서 자식요소로만 전달이 가능하다. props는 읽기전용으로 값이 변경되어서는 안 된다. props의 값을 변경하고 싶다면, props를 직접적으로 변경하는 것이 아닌 새로운 변수를 선언하여 사용한다.
State
는 컴포넌트 내부에 저장된 값이며, 수정이 가능한 값이다. state의 값을 변경할 때는 state 변수를 직접 변경하는 것이 아닌, setState 함수를 호출하여 변경한다. state는 컴포넌트의 동작을 제어하고 state의값이 변경되면 컴포넌트가 리렌더링된다.
props는 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터이고,
state는 현재 컴포넌트의 생명주기 동안에 변경될 수 있는 정보를 담고 있는 상태이다.