나는 정말로 웹 개발에 대해 잘 알고 있을까? (항시 업데이트)

LUCAS·2022년 8월 21일
5

면접 대비 시리즈

목록 보기
1/3

2019년 11월 22일, 내가 처음으로 프론트엔드 개발자로서 회사에 취업하게된 날이다.

당시 나는 웹개발에 대한 기초 지식이 많이 부족한 상태였고, 지금 생각해도 입사에 운의 요소가 많이 작용했던 것 같다.

수 년이 지난 지금, 과연 나는 JS를 잘알고 있는 걸까?
그 동안 다른 회사에 면접을 여러 번 보았지만, 그다지 어려운 면접 질문을 받은 기억은 없었다.

다만, 대한민국에서 개발자라면 누구나 가고 싶어하는, 네카라쿠배의 면접 질문도 과연 문제 없을까?
아무래도 꼬리에 꼬리를 무는 질문이 많을 것이다.

본고는 그러한 질문에 대해 대비를 하기 위해 작성을 하는 것이며, 이 과정에서 내 자신을 시험해보고자 한다.

또한 그 동안 해당 Velog를 통해 여러 면접 질문을 대비할 수 있는 포스트를 여럿 출간하였고, 그러한 포스트에는 북마크 및 조회수가 꾸준히 높았다는 것을 감안해, 여러분들께 설명을 드릴 수 있는 형태로 작성하고자 한다.

이전 포스트
프론트엔드 개발자 면접 준비 (javascript)
프론트엔드 개발자 면접 준비 (React)

글의 형태는 면접 질문에 대해 내가 가지고 있는 정보를 바탕으로한 답변, 그 답변에서 꼬리질문이 나올 수 있는 모든 단어들을 캐치해 설명을 풀어보는 식으로 작성을 진행할 것이다.


브라우저 기초

1. 브라우저가 웹페이지를 렌더링하는 과정에 대해 설명해주세요

  1. 사용자가 브라우저를 통해 웹페이지에 접근하게 되면, 브라우저는 웹페이지에 렌더링되는 요소(JS, HTML, CSS 등)를 서버를 통해 서빙받는다.
  2. HTML을 파싱해 DOM 트리를 만든다. 해당 DOM 트리는 HTML Element로 구성되어있다.
  3. 마찬가지로 CSS를 파싱해 CSSOM 트리를 만든다. 해당 CSSOM 트리는 각 HTML Element에 대한 CSS로 구성되어있다.
  4. DOM 트리와 CSSOM 트리를 통해 Render 트리를 구성한다. 이 과정을 통해 DOM 트리에 구성되어있는 각 노드에 CSSOM 트리 구성을 통한 CSS 스타일을 입힌다.
  5. Script 태그를 만날 경우, HTML의 파싱을 중단하고 Script 문법에 따라 자바스크립트를 파싱한다. 이 과정에서 자바스크립트 엔진은 AST(추상적 구문 트리)를 생성하는데, 이를 통해 인터프리터가 실행할 수 있는 중간 코드인 바이트 코드를 생성해 실행한다.
    다만, V8 오픈소스 자바스크립트 엔진을 통해 렌더링할 경우, 바이트 코드는 따로 생성하지 않는다.
  6. 브라우저의 뷰포트 내에서 각 요소가 화면의 어느 위치에 어느 정도의 크기로 배치될지에 대한 계산을 수행하며, 배치를 통한 레이아웃을 구성한다. 이 작업을 Reflow 작업이라 한다.
  7. Reflow 작업을 통해 구성된 레이아웃을 실제로 브라우저 뷰포트 내에서 그리는 작업을 진행한다. 이 과정을 Repaint 작업이라 한다.

참고 #1: https://artvelop.github.io/crp-page/

1-A. 위 과정이 완료된 후 CSS가 변경된다면, 브라우저의 렌더링 엔진은 이를 어떻게 처리하게되나요?

Case-By-Case인데, 변경에 사용한 CSS가 레이아웃에 영향을 주는지에 따라 처리 방식이 상이합니다.

만일, 변경에 사용된 CSS가 레이아웃에 영향을 끼치지 않는 Attribute라면, Reflow 작업을 생략합니다. 영향을 끼치지 않는 Attribute는 아래와 같습니다.

  • opacity, transform, box-shadow, zIndex 등

Reflow가 발생하면 변경되는 요소에 따라 레이아웃 배치를 계산해야하는 비용이 발생할 수 있기 때문에, 이를 피하는 것이 중요합니다.

참고 #1: https://docs.google.com/spreadsheets/u/0/d/1Hvi0nu2wG3oQ51XRHtMv-A_ZlidnwUYwgQsPQUg1R2s/pub?single=true&gid=0&output=html

1-B. 브라우저의 렌더링 성능을 개선할 수 있는 개발 방식이 있다면 설명해주세요.

첫 번째로 브라우저 렌더링 과정에서 설명드렸듯, Reflow를 최대한 지양하는 변경 방식을 사용해야합니다.
예로 요소를 이동 시키기 위해 left 혹은 top attribute를 사용하는 것 보다 transform: translate를 사용하는 것이 하나의 좋은 예시가 될 수 있을 것입니다.

두 번째로 변경 사항이 많이 발생하는 애니메이션의 경우 position을 absolute나 fixed를 사용해, 영향 받는 노드들을 최소화 하는 방식이 있을 것입니다.

세 번째로 하드웨어 가속을 사용하는 CSS 요소를 사용하는 것입니다.
하드웨어 가속을 적용할 요소에 translate3d(0,0,0)translateZ 등을 사용해 레이어를 분리 시키는 레이어 핵 작업을 사용하거나, will-change 요소를 사용해 브라우저에게 GPU 가속을 사용할 CSS Attribute를 알려줄 수 있다.

물론 무작정 모든 요소를 하드웨어 가속화 시키는 것은 오버헤드를 극대화시킬 수 있음으로, 유의하고 사용하는 것이 좋을 것입니다.

참고 #1: https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome/

2. SPA와 MPA에 대한 설명과 각 장점을 설명해주세요.

SPA는 Single Application의 줄임말로 하나의 웹페이지로 동작하는 웹어플리케이션을 뜻합니다.

우리가 사용하는 React, Vue, Angular 등이 이러한 SPA를 만들어주는 프레임워크에 속합니다.

사용자의 요청에 따라 초기에 하나의 웹페이지만을 받으며, 이후에 발생하는 사용자 인터렉션에 대한 처리에 대해서 JSON 형태의 데이터를 통한 최소한의 변화가 가능하기에 UX적인 측면에서 좋다고 볼 수 있습니다.

하나의 페이지에서 동작하기에 내부 데이터에 대한 캐싱이 조금 더 효과적이라고도 볼 수 있겠습니다.

MPA는 SPA와 반대되는 개념으로 Multi Application의 줄임말입니다.

여러개의 웹페이지로 동작하는 웹 어플리케이션을 뜻하며, 완성된 형태의 HTML을 서버로 부터 내려받아 서빙하기에, 검색엔진이 크롤링을 하기에 적합한 형태라 볼 수 있어 SEO에 친화적입니다.

사실 SPA를 설명할 때 SEO 관점에서 좋지 못하다고 하지만 서버사이드 렌더링을 통해 모든 요소를 HTML로 렌더링해 클라이언트로 서빙할 수도 있어, SEO에 완전히 불리한 것은 아닙니다.

2-A: 그렇다면 단점으로는 무엇이 있을까요?

우선 MPA의 경우 브라우저의 탭을 넘나들어야하니, 화면이 깜빡임에 따라 사용자 경험이 좋지 않을 수 있습니다.
또한, 라우트 특성 상 SPA 보다는 좀더 백엔드와 밀접한 관련이 있다고 볼 수도 있을 것 같습니다.

SPA의 경우 초기에 정적 리소스를 받아야한다는 특징 때문에 초기구동속도가 조금 늦을 수도 있습니다. 허나 이를 해결하기 위해 SSG, ISR등 다양한 방식으로 PreFetching이 가능하니, 어떤식으로 사용자에게 웹페이지를 서빙해주냐가 관건이겠습니다.

사실상 SPA의 단점이 MPA의 장점을 뜻하고, MPA의 단점이 SPA의 장점을 뜻할 수 있으니 가용 가능한 기술을 사용해 얼마만큼의 커버리지를 챙길 수 있느냐가 중요할 것 같습니다.

3. SSR과 CSR에 대한 설명과 각 장단점을 설명해주세요.

SSR은 서버사이드 렌더링의 약자이고, CSR은 클라이언트사이드 렌더링의 약자이다.

이름에서 찾아볼 수 있듯이, 의미 자체는 어떠한 사이드에서 렌더링을 할지에 대해 설명해준다.

클라이언트 사이드 렌더링은 서버로 부터 사용자에 의한 화면의 렌더링에 필요한 소스코드 및 HTML, 에셋들을 내려받고 렌더링을 진행하며, 서버 사이드 렌더링은 서버에서 사용자의 요청에 의해 렌더링을 진행한뒤, 사용자 화면에 보여줄 수 있을 정도로 렌더링된 HTML을 전달한다.

CSR은 사용자 친화적이라는 장점이 있습니다.
사용자 요청에 의한 부분만을 클라이언트 사이드에서 렌더링하여 보여줄 수 있기 때문입니다.
그렇지만 언급하였듯 처음부터 모든 HTML 컨텐츠를 가지고 있는 것은 아니기 때문에 SEO에 취약하다 볼 수 있습니다.

SSR은 SEO에 최적화되어있습니다.
하지만 TTV(Time To View)와 TTI(Time To Interact)의 간에 시간의 간격이 필연적으로 발생합니다.
페이지에 대한 마운트에 서버 렌더링이 관여되기 때문입니다.


JS 기초

1. 자바스크립트에 대해 아는 대로 설명해주세요.

자바스크립트는 일급 함수를 사용하는 객체 지향 인터프리터 언어이며 웹페이지의 스크립트 언어로 알려져있지만, 브라우저가 아닌 환경에서도 사용됩니다.
한 번에 하나의 명령만 처리할 수 있는 싱글 쓰레드 언어이며, 동기적으로 동작하기에 여러 개의 명령이 처리될 경우, 앞단의 명령이 처리되기 전까지 뒷단의 명령은 블럭킹이 됩니다.

현대의 브라우저와 nodeJS의 경우 V8이라고 하는 오픈소스 자바스크립트 엔진 위에서 동작하는데, 기존의 자바스크립트 엔진에서 V8 엔진을 사용하는 이유로는 속도 개선이 있을 것입니다.

웹페이지에서 동적인 요소 제공을 위해 사용되는 스크립트 특성 상, 유저의 인터렉션에 대한 즉각적인 대응이 되어야 하나, 이전에 설명드린 것과 같이 인터프리터 언어이기 때문에, 코드가 많아질 경우 속도가 현저히 느려진다는 단점이 있었습니다.

V8 엔진은 이러한 단점을 보완하기 위해 JIT 컴파일러를 사용해 좀 더 효율적으로 기계어 코드로 변환하는데, 자바스크립트 코드를 실행한 시점에서 인터프리터 방식으로 차례차례 불러와 기계어를 생성하면서, 그 코드를 캐싱하여 같은 함수가 여러 번 불릴 때 매번 같은 기계어 코드를 생성하는 것을 방지합니다.

자바스크립트를 설명할 때 매번 프로토타입을 함께 설명하고는 합니다.
자바스크립트는 기본적으로 프로토타입 기반 언어라고 불리우는데, 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체를 가진다는 의미입니다.

프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지입니다. 이를 프로토타입 체인이라 부르며 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 하는 근간입니다. (실행 컨텍스트 chain Scope)

이러한 동작 과정은 OOP의 상속 기능을 자바스크립트에서 구현한 것과 동일하다고 볼 수 있습니다.

자바스크립트는 ECMAScript Spec을 준수함에 따라 개발 생산성이 매번 크게 개선이 되는데, 이러한 부분 덕에 현대에서도 많은 사람들에게 사랑받을 수 있는 언어라 생각됩니다.

1-A. 일급 함수가 무엇인지 자세히 설명해주세요.

일급 함수란, 함수임에도 변수와 동일하게 취급되는 함수를 말합니다. 우리가 함수를 호출할 때, 인수로 함수를 사용하거나, 객체의 프로퍼티 값으로도 사용하는 것과 동일합니다.
우리는 함수를 배열에 넣을 수도 있고, 일반 변수와 동일하게 취급할 수 있습니다.
일반적인 변수와 함수의 차이점은 함수는 호출할 수 있다는 점입니다.

참고 #1: https://rinuel.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-FirstClass-Function%EC%9D%BC%EA%B8%89%ED%95%A8%EC%88%98-%EC%A0%95%EB%A6%AC

1-B. 블럭킹을 해결할 수 있는 방법들이 있다면, 이에 대해 설명해주세요.

블럭킹은 UX을 나쁘게하는 중대한 요소입니다.
자바스크립트는 이를 해결하기 위해 여러 기능들을 가지고 있습니다. (논-블러킹 구현)

첫번 째, 자바스크립트는 외부 요소들을 통해 비동기 처리를 지원합니다. 흔히 알고 있는 WEB API인 setTimeout 및 setInterval부터 Ajax 등등, 비동기 처리를 사용할 수 있도록 현대 브라우저를 통해 호스트 객체를 제공하고 있습니다.

간단히 setTimeout의 동작 방식을 예시로 들자면, setTimeout이 호출되어 콜백 스택에 적재되고 이 것이 실행된다면, 콜백 스택에서 바로 비워지고 Web API에서 입력한 지연 시간동안 첫 번째 인수로 입력한 콜백 함수를 가지고 있다가, 지연 시간이 만료되면 테스크 큐로 넘깁니다.
콜백 스택이 비워지면 테스크 큐의 프레임이 콜백 스택으로 적재되며 이러한 일련의 과정을 이벤트 루프라고 합니다.
이를 통해 자바스크립트는 동시성을 지원하며 논-블러킹을 구현할 수 있게 되었습니다.

만일 무거운 작업이고 이러한 WebAPI를 사용하지 않았다면, 이 작업이 처리될 때 까지 콜백 스택에 계속 적재되어있을 것이기 때문에 이후 호출 스택에 적재되는 모든 실행 흐름들이 블럭킹됐을 것입니다.

두 번째, Web Worker를 통해 해결할 수 있습니다.
Web Worker API는 WEB API 기능 중 하나입니다.
개발자는 싱글 쓰레드(단일 호출 스택)를 가지고 있는 자바스크립트에서, Web Worker를 사용해 추가적인 Worker 쓰레드를 사용할 수 있습니다.
Worker에서 Worker를 중첩되게 호출할 수 있으며, 기존의 글로벌 컨텍스트와 다른 글로벌 컨텍스트에서 실행되어 window 등을 사용할 수 없다는 단점이 있지만, 이것을 제외하고는 굉장히 자유로운 편입니다.
메인 쓰레드와 Worker 쓰레드에서 PostMessaging을 통해 양 간 통신이 가능하며, terminate 메소드를 통해 Web Worker를 종료할 수 있습니다.

참고 #1: https://developer.mozilla.org/ko/docs/Web/API/Web_Workers_API

2. setTimeout을 중첩해서 사용해 setInterval의 동작과 동일하게 하려고 하는데, 이렇게 하면 발생할 수 있는 문제가 있을까요?

지연 시간에 대한 보장에 차이가 있습니다!

재귀되는 setTimeout의 경우, 이후 실행되는 setTimeout의 텀의 보장 시점은 함수가 종료되는 시점에 있습니다.

다만, setInterval의 경우, 인수로 사용된 콜백의 실행이 종료되지 않아도, 두번 째 인수로 입력된 지연 시간이 지나면 연이어 콜백을 재호출하기 때문에, 해당 API의 텀 보장 시점은 함수가 실행되는 시점이라 볼 수 있습니다.

참고 #1: https://javascript.info/settimeout-setinterval

3. 브라우저에서 사용하는 Queue 종류와 각 Queue에 대한 설명을 부탁드립니다.

Task Queue, Microtask Queue, Animation frame Queue가 있으며 모두 이벤트 루프를 통해 각 Queue에 대한 우선순위가 부여되고 있습니다.

Task Queue는 setTimeout, setInterval, setImmediate와 같은 Task API들이 처리되는 공간이며,
Microtask Queue는 Promise나 Async/Await, process.nextTick이나 Object.observe, MutationObserver 등과 같은 비동기 호출을 넘겨 받는 공간입니다.

마지막으로 Animation Frames Queue는 requestAnimationFrame과 같이 브라우저의 렌더링과 관련된 Task를 넘겨받는 Queue입니다.

처리 순서는 Microtask Queue, Animation Frames Queue, Task Queue 순이며 이는 현 시점의 V8 엔진 기준으로 다른 자바스크립트 엔진에서는 다를 수 있습니다.

특이 사항으로, 이벤트 루프에서 각 Queue에 담긴 Task에 대해 호출할 때, Microtask Queue와 Animation Frames Queue는 모두를 호출하는 반면, Task Queue는 호출 시 단 하나의 작업만 Call Stack으로 전달하고 다른 Queue를 순회합니다.

4. 엄격모드('use strict')에 대해 설명해주세요.

엄격모드('use strict')은 자체적으로 전체 스크립트나 개별함수에 대해 다양한 문법을 제한할 수 있도록 하는 명령문입니다.
ECMAScript 2015부터는 모듈에 한해 기본적으로 엄격모드가 처리돼 있습니다.

엄격모드는 다음과 같은 특징이 있습니다.
1. 전역 변수 사용을 제한해, memory leak이 발생하는 것을 최소화합니다. 가비지 컬렉터는 전역으로 할당된 변수에 대해 회수가 불가능하기 때문입니다. 마찬가지로 Javascript의 최적화를 방해하는 요소를 바로잡을 수 있게합니다.
2. 서로 다른 함수가 동일한 매개변수 명을 가지지 못하도록 합니다.
3. this를 통한 전역컨텍스트 접근이 불가능합니다. 접근하고자하면 undefined를 반환합니다.
4. 원시타입의 변수에 프로퍼티를 추가하는 것에 오류가 발생합니다.
5. 이 외로, 예외적인 상황에서 발생할 수 있는 문제들을 사전에 막을 수 있습니다.
6. Function.caller, Function.arguments 등에 접근할 수 없게되지만 엄격모드가 차단하는 기능에 대해 의존할 필요가 없습니다.

몇 가지는 평소에 개발할때에도 오류가 발생해 시도하지 않는 개발 방식인데, 위에서 말씀드렸듯 ECMAScript에서 현대 모던앱에 대한 모듈화 패턴에 자동적으로 엄격모드가 처리되도록 스펙을 변경했기 때문입니다.

5. mutable object와 immutable object에 관해 설명해주세요

자바스크립트에서의 변수 타입은 원시 자료형과 참조 자료형으로 분류됩니다.
원시 타입의 경우 string, number, bigint, boolean, null, undefined, symbol 이 있고 이 외의 나머지 자료형은 참조 타입이 됩니다. (Function, Object, Array)

6. 객체를 immutable하게 만드는 방법을 설명해주세요.

다음과 같은 Object Prototype Method 를 통해 객체를 불변하게 만들 수 있습니다.
1. Object.freeze
2. Object.defineProperty, 3번째 인수로 옵션 객체에 configurable 및 writable이 false로 되어야 함 (기본값 false)

7. 프로토타입에 대해 설명해주세요.

자바스크립트는 기존의 객체를 복사해 새로운 객체를 생성하는 프로토타입 기반 언어입니다.

우리가 Array를 만들어 사용할 때, map과 filter 등과 같은 Prototype Method를 사용하는 것 처럼 모든 객체는 자신의 원형이 되는 객체를 가지고 있고, 이러한 것을 프로토타입이라고 합니다.
부모 객체의 프로퍼티 및 메소드를 상속받아 사용할 수 있다는 면에서 객체 지향의 상속 개념과 비슷하다고 볼 수 있습니다.

7-A. 프로토타입 체인에 대해 설명해주세요.

예를 들어 설명드리겠습니다.
자바스크립트에서 함수는 객체입니다.
함수를 생성하고 __proto__ 접근자를 통해 prototype을 확인하면 Object의 prototype을 확인할 수 있게 됩니다.

이 처럼 접근자가 가르키는 링크를 따라 자신의 부모 역할을 하는 프로토타입을 순차적으로 체이닝하는 것을 말합니다.

  • 만약 __proto__ 접근자를 사용했을 때 undefined 가 반환된다면, 그것은 V8 엔진에서 각 타입에 대한 일관성을 유지하기 위해 의도적으로 차단된 것입니다.

참조 #1: https://stackoverflow.com/questions/7688902/what-is-functions-proto

8. 변수 선언 키워드에 대해 설명해주세요.

변수 선언 키워드는 세 가지가 있습니다.
var과 let, const가 있는데 var를 제외한 나머지는 ECMAScript2015(이하 ES6)부터 추가된 변수 선언 키워드입니다.

이 두 유형의 차이로는 세 가지가 있습니다.

첫 째, 할당되는 스코프가 다릅니다.
var의 경우 함수레벨 스코프를 취하고, let과 const의 경우 블럭레벨 스코프를 취합니다.
이 둘의 차이 점은 단어 그대로 "어떠한 스코프 내에서 참조가 가능한가" 인데, var의 경우 함수레벨 스코프라 함수 내부에 선언되어 있을 경우 함수 외부에서 해당 변수에 대한 참조가 불가능해지고, let과 const의 경우 블럭레벨 스코프이기 때문에 조건문, 혹은 while문 등, 중괄호로 이루어진 블럭 내부에서 선언되었다면 외부에서 해당 변수에 대한 참조가 불가능해집니다.

둘 째, 재할당 가능 여부가 다릅니다.
var의 경우 선언 후 재할당이 얼마든지 가능하지만, let과 const의 경우 원칙상 재할당이 불가능합니다.

셋 째, 호이스팅 동작 방식이 상이합니다.
변수는 세 가지의 단계로 선언되는데 선언과 초기화, 그리고 할당입니다.

var의 경우 선언과 메모리에 할당하는 초기화 처리가 동시에 이루어지게 되어, 선언된 변수에 대한 참조가 가능합니다.
하지만 let과 const의 경우 선언만 이루어지게 되어, 만일 소스코드의 9번째 라인에서 let과 const를 통해 변수를 선언했다면 그 이전 라인에서는 해당 변수에 대한 참조가 불가능해집니다.
이러한 영역을 Temporal deadzone이라 칭합니다.

9. 실행 컨텍스트에 대해 설명해주세요.

ECMAScript Spec에서 실행 컨텍스트를 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이라고 설명하고 있습니다.
이렇듯, 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경을 의미합니다.
실행 컨텍스트는 스코프, 호이스팅, This, 함수, 클로져의 동작원리를 담고 있는 자바스크립트의 핵심 원리입니다.

우리가 작성한 코드가 실행되면 전역 컨텍스트 스택 프레임 위에서 여러 컨텍스트가 쌓이고 LIFO에 의해 반환됩니다.
실행 컨텍스트는 물리적으로 세 가지의 형태를 갖고 있습니다.
첫 번째는 변수 객체(VO)입니다.
실행 컨텍스트가 생성이 되면, 자바스크립트 엔진은 실행을 위해 필요한 여러 정보들을 담을 객체를 생성하는데, 이 것이 변수 객체입니다.
전역 컨텍스트는 변수 객체 안에 전역 함수 및 전역 변수를 저장하며, 함수 컨텍스트인 경우 이와 더불어 Argument Object를 함께 저장합니다.

두 번째는 스코프 체인입니다.
스코프체인은 일종의 리스트로써 전역 객체와 중첩된 함수의 레퍼런스를 차례로 저장합니다.
전역 또는 함수가 참조할 수 있는 변수 객체에 대해 저장하게 되며, 이는 실행 컨텍스트에서 상위 컨텍스트와 그 상위 컨텍스트의 변수 객체, 마침내에는 전역 객체까지 참조할 수 있게 됩니다.
객체의 내부 프로퍼티를 보시면 [[scope]]가 있는데 해당 프로퍼티를 통해 Scope 배열에 접근이 가능하며, 자신을 포함하는 외부 함수의 실행 컨텍스트가 소멸되어도 [[scope]]가 가르키는 외부 함수의 실행 컨텍스트는 유지되는 특징이 있습니다.
이러한 원리를 통해 클로저의 동작 방식을 이해할 수 있습니다.

또한 스코프의 참조를 통해 우리는 자신이 실행하고 있는 컨텍스트 외부의 컨텍스트까지 변수를 찾을 수 있게 되고 이러한 것을 프로토타입 체인이라 칭합니다.

마지막 세번 째는 This Value입니다.
this에 할당 되는 값은 함수의 호출 패턴에 의해 결정됩니다. (this 설명에서 다루겠습니다.)

10. 클로저에 대해 설명해주세요.

클로저는 논란이 있는 토픽이기에 제가 알고 있는 것을 토대로 설명을 드리고자 합니다.
클로저는 단순히 선언 당시의 함수가 스코프 체인의 도움으로 전역 범위에 도달할 때까지, 현재 범위와 상위 함수의 범위, 그리고 그 상위 함수의 범위까지에 대한 변수와 매개 변수를 기억하는 능력입니다.

10-A. 렉시컬 스코프에 대해 더 상세히 설명해주세요. (+ 보충 필요)

렉시컬 스코프를 설명할 때마다 스코프 체인을 함께 설명하는데,
여러 중첩 함수가 있다고 가정을 해두었을 때, 중첩된 함수에서 글로벌 스코프의 변수를 참조한다고 가정했을 때, 중첩된 함수는 글로벌 스코프까지 스코프 체이닝을 하게 됩니다.

이렇게 자신의 스코프에서 실행 환경에 따라 영향을 받아 참조할 수 있는 스코프를 렉시컬 스코프라 칭합니다.

11. Class란 무엇인가요? (+ 공부 필요)

Javascript는 ES6에서부터 등장한 객체의 템플릿입니다.

클래스도 함수 처럼 표현식이 있는데, ...

12. ES6 Modules에 대해 설명해주세요.

과거에 모듈을 내보내고 불러오기 위한 여러 방법들이 있었습니다.
대표적으로 COMMONJS와 AMD 였습니다.
CommonJS 경우 NodeJS 기반에서 자주 사용되었었고, ES6 Modules이 등장하기 이전에는 브라우저 기반의 소스코드에서도 많이 사용되었습니다.
모듈을 불러오기 위해 require을 사용하고 내보내기 위해 exports 객체에 담는 형태로 사용했었습니다.
AMD의 경우에는 이름에서도 그렇듯 비동기적으로 모듈을 로드하기 위해 고안된 방법이었는데 최근에 모듈 공부를 위해 검색해보았지만 매우 생소한 사용 방식이 었던 것으로 기억합니다.

ES6 Modules은 모듈 로드 방식에 표준을 정하기 위해 탄생하게 되었습니다.
ES6 Modules은 import, export, from, default 등 모듈 관리 전용 키워드를 사용하기 때문에 가독성이 좋다고 볼 수 있습니다.

비동기 기반의 로드 방식이기 때문에 보다 빠르고, Renaming Import/Export나 Object Destructuring 문법을 통한 일부만 Import 하는 등 간단하게 많은 기능들을 구현하고 있습니다.

다만 모든 브라우저에서 지원하는 것은 아니어서, babel을 통해 CommonJS Transpiling을 하는 작업이 필요하기도 했습니다.

13. Set 오브젝트에 대해 설명해주세요.

Set 오브젝트는 ES6에서 등장한 중복을 제거한 값들의 집합입니다.
new키워드와 함께 사용해, 생성자로 중복을 제거하고 싶은 값 배열을 제공하면 중복이 제거된 Set 오브젝트가 반환됩니다.
Set 오브젝트에 정의된 프로토타입 메소드를 사용해 set 오브젝트에 값을 추가하거나 제거, 나열할 수 있습니다.

14. Callback 함수에 대해 설명해주세요.

15. Spread Operator와 Rest Operator에 대해 설명해주세요.

16. '%'을 사용하지 않고 짝수를 구분하는 방법을 알고계신가요?

17. AJAX가 무엇인가요?

18. Object.freeze와 Object.seal에 대한 차이를 설명해주세요.

19. hasOwnProperty와 in operator의 차이점에 대해 설명해주세요.

20. new 키워드가 하는 역할에 대해 설명해주세요.


네트워크

1. 프록시 서버의 역할과 동작 원리에 대해 설명해주세요.

프록시 서버는 백엔드와 프론트 사이에서 특정한 역활을 수행하는 서버를 말하며, 포워드 프록시와 리버스 프록시로 분리를 할 수 있습니다.

포워드 프록시의 경우, 프론트에서 백엔드 측으로 요청하는 수많은 정적 데이터에 대해 프록시 서버를 연계하도록 하여, 이전에 사용한 정적 데이터를 캐싱하고, 이에 대한 재요청에 캐싱된 데이터를 반환하도록 하도록 할 수 있습니다.

리버스 프록시의 경우, 포워드 포록시와 반대되는 성격으로 다수의 백엔드가 있다고 가정했을 때, 프론트가 프록시 서버로 요청을 보내면, 요청 유형에 따라 프록시 서버가 어느 백엔드에 프론트의 요청을 보낼지에 대해 정하는 역할을 수행할 수 있습니다.

Nginx 및 Apache 등 대다수의 HTTP 프레임워크는 프록시 기능을 제공하고 있습니다.

참고 #1: https://jcdgods.tistory.com/322

1-A. 그렇다면 프록시 서버를 통해 얻을 수 있는 이점은 무엇이 있을까요?

프록시 서버를 사용하면 얻을 수 있는 장점이 여럿 있는데, 첫째는 포워드 프록시와 같이 캐싱 기능을 사용할 수 있게 되며, 둘째로 프론트에서 프록시 서버로만 요청을 보내면되기 때문에 다수의 백엔드에 IP를 부여하지 않아도 되어 비용적 절감을 할 수 있게됩니다.
셋째로 프론트는 실질적으로 프록시 서버로만 API 요청을 보낼 수 있기에, 내부 서버에 대한 직접적인 접근을 차단할 수 있어, 보안적 요소를 얻을수 있게 됩니다.

추가적으로 리버스 프록시를 통해 백엔드는 자신에게 요청한 프론트의 정보를 알 수 없기 때문에 IP를 우회할 수 있다는 특징도 있습니다.

2. Cross Origin이 무엇인가요?

웹 어플리케이션은 기본적으로 Same-Origin Policy를 준수합니다.
이 뜻은 한 출처에서 실행 중인 웹 어플리케이션이 다른 출처의 서버에 접근할 수 없다는 뜻인데, 이때 Cross Origin Resource Sharing, 이하 CORS을 통해 이를 해결할 수 있습니다.

CORS를 사용하면 추가 HTTP 헤더를 통해 다른 출처에 대한 접근이 가능하도록 브라우저에 알려줄 수 있습니다.

여기서 동일한 출처란, 도메인과 프로토콜 및 포트가 같은 것을 의미합니다.

참고 #1: https://developer.mozilla.org/ko/docs/Web/HTTP/CORS


3. TTV와 TTI에 대해 설명해주세요.

TTV는 사용자가 웹사이트에서 컨텐츠를 볼 수 있는 시점을 의미하고, TTI는 사용자가 웹사이트에서 상호작용할 수 있는 시점을 의미합니다.

CSR의 경우 index.html을 서버에서 받아오고, 엔트리에 대한 Javascript까지 받아오게 되면 컨텐츠가 화면에 그려지고 상호작용할 수 있기 때문에 TTI와 TTV의 시점이 같다고 볼 수 있습니다.

다만 SSR의 경우 완성된 index.html을 받고 바로 컨텐츠를 확인할 수 있게 되지만, 서버에서 Javascript까지 받아오는 시간과 텀이 발생해 TTI와 TTV의 시점이 다르다고 볼 수 있습니다.

4. TCP와 UDP에 대한 차이를 설명해주세요.

TCP UDP는 모두 네트워크에서 사용하는 데이터 통신 방식입니다.

두 포로토콜의 차이는 헤더를 보면 명확한데, TCP인 경우 세그먼트가 전달이 정상적으로 이루어졌는지 확인할 수 있는 데이터가 헤더에 포함되어 있으나, UDP인 경우 그렇지 못합니다.

따라서 장점과 단점이 나뉘어지게 되는데 TCP의 경우 데이터에 신뢰성이 있지만 UDP는 그렇지 못합니다.
세그먼트에 대한 신뢰성을 지키기 위해 TCP의 헤더가 UDP에 비해 크기 때문에 속도 또한 TCP가 UDP에 비해 느립니다.

5. 3-handshake에 대해 설명해주세요.

TCP 3-Way handshake는 TCP/IP 프로토콜을 이용해서 통신을 하는 응용프로그램이 데이터를 전송하기 전에 먼저 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정을 의미합니다.

[STEP 1]
A 클라이언트는 B 서버에 접속을 요청하는 SYN 패킷을 보내고 SYN/ACK 응답을 기다리는 SYN_SENT 상태가 됩니다.

[STEP 2]
B 서버는 SYN 요청을 받고 A 클라이언트에게 요청을 수락한다는 ACK와 SYN Flag가 설정된 패킷을 발송하고 A가 다시 ACK으로 응답하기를 대기합니다. 이때 B서버는 SYN_RECEIVED 상태가 됩니다.

[STEP 3]
A 클라이언트는 B 서버에 ACK을 보내고 이후로 연결이 이루어지며 데이터가 오갈 수 있다고 규약을 맺습니다.
이때 B서버의 상태는 ESTABLISHED입니다.

이러한 방식으로 신뢰성 있는 연결을 맺어주는 것이 TCP의 3 Way handshake 방식입니다.

6. TTL에 대해 설명해주세요.

TTL은 Time To Live의 약자입니다.
네트워크에서 패킷이 만료되기 까지의 수치를 나타냅니다.
패킷이 네트워크에서 무한정으로 떠돌지 않기위해 TTL을 부여하며, 각 라우터에서 패킷을 처리할 때 마다 TTL의 수치를 감소시킵니다.
만약 TTL이 0 값이 되면 송신측 라우터에서 ICMP TIME EXCEEDED 메시지를 되돌려보내고 패킷이 폐기되었다는 사실을 알려줍니다.

ReactJS (NextJS 포함)

1. useEffect는 Dependancy에 대해 비교할 때 어떻게 수행하나요?

React의 기본 훅인 useEffect는 Dependancy의 변경 사항에 대한 비교를 수행할 때, 얕은 비교(Shallow Compare)을 수행합니다.
얕은 비교로 인해 Dependancy Check는 아래와 같은 특징을 가지게 됩니다.

  1. 원시타입의 경우 문제가 되지 않겠지만, 참조 타입의 경우 해당 레퍼런스를 통해 비교를 수행하기 때문에, 내부 프로퍼티가 동일하다고 하여도 라이프 사이클이 만료되게 됩니다.

  2. useEffect의 Dependancy List에 useEffect와 동일한 컨텍스트에서 선언된 함수가 있다면, useCallback으로 해당 함수를 감싸는 이유도 이 때문입니다.

2. ReactJS의 라이프사이클에 대해 설명해주세요.

라이프사이클(수명 주기)은 컴포넌트가 페이지에 마운팅되기 전인 준비 과정에서부터 시작하여 언마운팅이 되는 것에 대한 전체적인 컴포넌트의 수명 주기를 의미합니다

리액트의 클래스형 컴포넌트에서는 라이프사이클을 조정할 수 있는 다양한 라이프사이클 메소드가 있습니다.
우리가 아는 componentWillMount, componentWillUnMount, componentDidUpdate 등이 이러한 메소드입니다.

이러한 메소드들을 통해 라이프사이클 흐름에 대해 개발자가 관여할 수 있게됩니다.
함수형 컴포넌트에서는 이러한 라이프사이클 메소드 역할을 useEffect 훅이 대신합니다.

3. SSG, SSR, ISR에 대한 각 설명과 차이점을 설명해주세요.

서버사이드 렌더링은 우리에게 많은 이 점을 가져다주었습니다.
렌더링 환경이 유지돼 호환성이 좋아졌으며, 클라이언트 사이드 렌더링이 가지는 고질적인 문제인 SEO를 피할 수 있었습니다.
하지만 장점만 있는 것은 아니었습니다.

사용자 수가 많은 웹 애플리케이션에서 서버 사이드 렌더링에 관여되는 부분이 많아질 수록 서버에 부하가 심해질 수도 있습니다.
이러한 이슈를 해결하기 위해 만들어진 기술이 SSG와 ISR입니다.

이 기술의 원리는 단순합니다. SSR은 사용자가 접속할 때 마다 서버 사이드에서 빌드를 하는 반면, SSG는 초기 빌드 시 한 번밖에 빌드하지 않습니다.
물론 SSG 기술은 한 번밖에 빌드되지 않는 다는 장점아닌 단점으로 다가올 수 있어, 이러한 기술은 데이터가 변하지 않는 정적인 데이터 노출에 사용되어 왔습니다.

ISR은 SSG와 흡사하나 동작 방식이 조금 다릅니다.
SSG와 같이 초기 빌드 시 한 번만 빌드하나, 개발자가 지정해둔 시간 값을 통해 해당 주기로 빌드를 반복하게 됩니다.
물론 사용자가 접속했을 경우에만 빌드하기 때문에 IDLE 상태에서는 부담이 없습니다.

해당 내용은 제가 NEXTJS를 사용하면서 알게된 PAGE 라이프사이클 API를 통해 설명드리는 내용입니다.

4. 프롭 드릴링은 왜 발생하고, 어떻게 피할 수 있을까요?

프롭 드릴링은 순전히 하위 컴포넌트로의 데이터 전달을 위해 컴포넌트 간 프롭이 전달되어 가는 것을 말합니다.
컴포넌트 기반의 리액트에서는 필연적으로 발생할 수 있는 문제이며 이미 이를 해결하기 위한 수 많은 솔루션이 있습니다.

  1. ContextAPI 사용
    React가 제공하는 ContextAPI를 사용합니다.
    createContext를 통해 Provider 컴포넌트를 작성해 해당 Provider의 Children에 포함된 컴포넌트에서 useContext를 사용한 데이터 활용이 가능합니다.

  2. 외부 Store 사용
    컴포넌트에서 분리되어 있는 Store를 참조하도록 해 컴포넌트 간 종속성을 피할 수 있습니다.
    Redux, Mobx, Recoil, Jotai 등의 상태 관리 라이브러리로 이를 해결할 수 있습니다.

  3. 컴포넌트 아키텍쳐의 재설계
    가장 간편하고 라이브러리를 사용하지 않아도 되는 방법입니다.
    사실 제가 리액트로 프로젝트를 다룰 때 초기에 아키텍쳐 설계에 매우 신경 쓰는 이유가 이 프롭 드릴링 때문인데, 종속성 있는 컴포넌트의 설계는 프롭 드릴링의 문제도 있을 뿐더러 성능 최적화 관점에서 좋지 못합니다.
    예를 들어 Atomic Design Pattern을 사용한다면, Atom 컴포넌트에서 부터 Page 까지 렌더링 되기 위해 최대 5번의 Prop Drilling이 발생할 수 있는데, 이를 피하기 위해 Atomic Design Pattern을 그대로 수용하는 것이 아닌 프로젝트 규모에 따라 Atom 컴포넌트까지만 수용하는 등 다양한 선택을 통해 초기에 설계하는 방법이 있습니다.


CICD(지속적 통합/제공) + 인프라

1. 프로젝트를 구성하실 때 ECR과 ECS를 사용하셨다 하셨는데, 어떤 식으로 구성하셨나요?

개인 프로젝트 같은 경우 설정 비용값이 그다지 크지 않은 EC2를 사용했지만, 인프라가 어느정도 구축된 경우 서비스가 구축된 DockerImage를 Build 하고 ECR에 배포해, 작업 정의를 통해 컨테이너에 올려 서비스될 수 있도록 처리 했습니다.

1-A. EC2와 달랐던 점은 무엇이었나요?

작업 개수 할당을 통한 무중단 배포가 가능했다는 점, 문제가 발생하면 ECR에서 자동으로 이전 버전의 도커 이미지를 사용해 롤백이 쉬웠다는 점, 로드 벨런서를 통해 유연하게 서비스 제공이 가능했다는 점이 있었습니다.

EC2는 인스턴스를 만들어 제공 가능한 서비스를 호스팅한다는 느낌이 강했다면 ECS는 컨테이너를 운용한다는 느낌이 강하게 들었습니다.

2. 자동화 배포를 위해 Github Action을 사용하셨다 하셨는데, 스크립트는 어떻게 구성하셨나요?

Github Action은 다양한 스크립트를 개발자들이 쉽게 사용할 수 있도록 스크립트 마켓이 있습니다.
대다수의 유용한 스크립트가 그 곳에 있었고 저는 이러한 도구를 적극 활용했습니다.

첫 번째로 NextJS CLI의 Export 명령어를 사용해 실행가능한 HTML파일로 추출하는 스크립트를 실행하도록 명령어를 구성하였고,
추출된 디렉토리를 CloudFront와 연동이 되어있는 S3로 업로드하는 Step, 최종적으로 CloudFront의 Invalidation(무효화)을 진행하는 Step으로 자동화 배포가 이루어지게 끔 처리했습니다.

3. 테스트 코드를 작성하실 때 규칙이 있나요? 테스트 커버리지 보장을 위해 어떻게 테스트 코드를 구성하시나요?

저는 테스트 코드를 작성할 때 세 가지 유형을 염두합니다.

  1. UI 테스트 (스냅샷 테스트)
  2. 기능 테스트 (Unit 테스트)
  3. 사용자 테스트 (E2E 테스트)

가능한한 TDD 방식으로 코드 설계를 하며, 사용자가 보는 UI 컴포넌트에 대해 스타일의 일관성을 부여하기 위한 부분은 스냅샷을 사용하고, 버튼이 호버되거나 눌리거나, 기타 컴포넌트의 기능에 대해 개발을 진행할 때는 기능 스펙을 작성하고 해당 스펙에 따라 테스트 코드를 작성합니다.
작게는 호버나 기타 유저 인터렉션에 따라 컴포넌트가 정확하게 동작했는지를 검증하기 위한 코드에서 부터 크게는 작성한 커스텀 훅이 동일한 인자가 정확한 Output을 내는지 테스트 코드를 작성합니다.

개발이 완료된다면 cypress 등을 통해 End To End 테스트 코드를 작성합니다.

테스트 커버리지를 최대한 보장하기 위해 컴포넌트를 개발할 때 순수 UI에 대해서 테스트를 진행할 수 있게끔 고차컴포넌트(HOC)를 통해 컴포넌트에 비즈니스로직을 주입하는 패턴을 사용해 아키텍쳐를 스스로 설계했습니다.

이 아키텍쳐의 장점으로는 비즈니스 로직을 완전히 분리해 테스트 할 수 있다는 점에서 커버리지를 최대한 끌어올릴 수 있다는 점이 있습니다.

WEBPACK + @

1. 웹팩에 대해 설명해주세요.

웹팩은 쉽게 말하자면 모듈 번들러입니다.
과거에는 Javascript가 웹에 지금처럼 커다란 역할을 갖지 못하였습니다.
하지만 기술의 발전에 따라, 현대의 모던 웹으로 오기 까지 Javascript의 영향력은 전과 비교할 수 없을 정도로 방대해졌습니다.

1-A. 웹팩을 통해 얻을 수 있는 장점으로 무엇이 있을까요? (*)

  1. 번들링을 통해 번들 파일을 minify 혹은 난독화를 할 수 있습니다.
  2. Loader를 정의할 수 있는데, Babel-Loader을 사용해 ES6을 지원하지 않는 브라우저에서 코드를 트랜스파일링하여 지원할 수 있습니다.

2. 프로덕션 혹은 개인 프로젝트에서 웹팩을 사용하신 적이 있으신가요? 있다면 왜 사용하셨나요?

사실 요즘 CRA와 같은 CLI을 통해 작성되는 보일러플레이트의 경우 웹팩이 기본적으로 내장되어 있어 별도의 설정없이 사용 가능합니다.

다만 웹팩 설정에 직접 접근이 필요할 경우 CRA에서는 eject 명령어를 통해 설정파일을 수정할 수 있습니다.

저의 경우 babel 설정파일을 직접 수정해 사용했는데, Modularize로 Import를 최적화 하거나 Emotion 라이브러리의 사용을 위한 목적이 대부분이었습니다.

프로그래밍

1. FP 특징과 장점에 대해 설명해주세요.

1-A. 고차 함수에 대해 설명해주세요.

함수를 반환하거나 함수를 인자로써 취하는 함수를 의미합니다.

2. OOP의 특징과 장점에 대해 설명해주세요.

3. 명령형/선언형 프로그래밍에 대해 설명해주세요.

4. ECMAScript에 대해 설명해주세요.

5. 메모이제이션에 대해 설명해주세요.

6. SOLID에 대해 설명해주세요.

SOLID는 객체지향 개발의 5대 원리입니다.
5가지 원리의 앞글자를 따 SOLID라 칭하며 각각 단일 책임의 원칙, 개방폐쇄의 원칙, 리스코브 치환의 원칙, 인터페이스 분리의 원칙, 의존성 역전의 원칙입니다.

  • 단일 책임의 원칙
    단일 책임의 원칙은 말 그대로 함수 및 컴포넌트(이하 클래스)가 한 가지의 기능만 하도록 제한을 두어야 한다는 뜻입니다.
    클래스가 제공하는 모든 서비스는 그 하나의 책임을 수행하는 것에만 집중되어 있어야 하며, 이는 어떤 변화에 의해 함수 및 컴포넌트를 변경해야 하는 이유는 오직 하나뿐이어야함을 의미합니다.
    하나만의 기능을 가진 함수로 분리되기에 확장이 용이하며 유지보수하기 좋고 가독성이 좋은 코드를 작성할 수 있게 됩니다.

  • 개방폐쇄의 원칙
    소프트웨어의 구성요소는 수정에는 닫혀있고 확장에는 열려있어야한다는 원리입니다.
    이는 요구 사항에 의해 기존의 코드에 수정이 필요하더라도, 기존 구성요소에 대한 수정이 일어나지 말아야 하며, 기존 구성 요소를 쉽게 확장해서 재사용할 수 있어야 한다는 뜻입니다.
    해당 원칙이 지켜지기 위해서, 코드는 다형성을 제공하며 추상적이어야 하며, 개방폐쇄의 원칙을 따르기에 기존 코드를 수정함에 있어서 발생하는 사이드 이펙트를 최소화할 수 있다는 장점이 있습니다.

  • 리스코브 치환의 원칙
    서브 타입은 언제나 기반 타입으로 교체할 수 있어야한다는 원리입니다.
    리액트는 컴포넌트 기반 프레임워크로 기본적으로 상속(inherit)이 아닌 합성(composition)으로 구성되어 있기 때문에 해당 원칙을 활용하기는 어렵습니다.

  • 인터페이스 분리의 원칙
    자신이 사용하지 않는 인터페이스는 구현하지 말아야 한다는 원리입니다.
    하나의 일반적인 인터페이스 보다는 여러개의 구체적인 인터페이스가 해당 인터페이스를 활용하는 컴포넌트 입장에서 더 좋다는 의미입니다.
    리액트의 관점에서는 컴포넌트들 간의 의존성을 최소화해 컴포넌트의 결합도를 낮추고 재사용성을 높일 수 있다는 특징이 있습니다.
    인터페이스를 분리해 사용하면 각 컴포넌트가 의존성을 최소한으로 지닐 수 있도록 자성할 수 있기 때문입니다.

  • 의존성의 역전
    구조적 디자인에서 발생하던 하위 레벨의 모듈 변경이 상위 레벨의 모듈 변경을 요구하는 위계관계를 끊는 의미의 역전입니다.
    중요한 것은 각각의 클래스, 즉 각각의 컴포넌트들이 가지고 있는 의존성을 주입받도록 하여, 하위 레벨의 모듈 변경에도 그 기능 자체가 의존성을 가지고 있지 않기 때문에 영향을 받지 않아도 된다는 것에 있습니다.

profile
안녕하세요! FE개발자 최근원입니다.

0개의 댓글