Reference: 지금까지 받았던 신입 프론트엔드 면접 질문들 by arthur
주의 : 세상에서 제일 강한 타입 OCaml을 공부하다 세상에서 제일 약한 타입 JS를 정리해서 집중력이 좀 떨어져 있을 수 있습니다. 모든 피드백 환영합니다.
자바스크립트는 본래 브라우저를 제어하는 언어였으나, Node.js
라는 새로운 실행환경과 구글 V8 엔진의 등장으로 자체 어플리케이션을 만들 수 있게 된 언어입니다.
또한 자바스크립트는 명령형, 함수형, 객체지향 프로그래밍이 모두 가능한 멀티 패러다임 언어입니다.
변수 선언은 변수를 생성하는 것을 의미합니다. 해당 식별자를 등록하여 스코프가 참조할 대상을 만듭니다.
초기화는 메모리에 변수 저장을 위한 공간을 확보하는 단계입니다. 기본값으로 undefined
가 할당됩니다.
할당은 =
연산자를 사용하여 값을 할당하는 단계로, undefined
로 초기화된 변수에 실제 값을 할당해주는 단계입니다.
JavaScript의 타입은 원시값과 객체로 나뉩니다.
원시 값은 불변값을 의미하며, boolean
, null
, undefined
, number
, bigint
, string
, symbol
타입이 있습니다.
객체는 키와 값 사이의 매핑이며, 키는 문자열 또는 심볼입니다. 값은 어떤 것이든 가능합니다.
또한 함수는 호출이 가능하다는 점을 제외하면 일반적인 객체입니다.
생성자 함수란 new
연산자와 함께 호출하여 객체를 생성하는 함수를 의미합니다.
생성자 함수에 의해 생성된 객체를 인스턴스라 하며, 자바스크립트는 Object
외에도 다양한 타입의 빌트인 생성자 함수를 제공합니다.
생성자 함수는 일반 함수와 동일한 방법으로 정의하지만, 파스칼 케이스를 사용하는 관례를 따릅니다.
this
키워드는 자신이 속한 객체를 가리키는 식별자를 참조할 수 있는 키워드입니다.
this
는 함수가 호출되는 방식에 따라 달라집니다.
this
가 없기 때문에, 선언될 시점에서의 상위 스코프가 바인딩됩니다. 이 세가지 메서드는 강제로 this
를 바꿉니다.
call
과 apply
는 함수를 호출하는 함수입니다. 그러나 첫번째 인자에 this
로 만들고 싶은 객체를 넘겨주어, this
를 바꾸고 나서 실행합니다.
이 두 함수의 차이점은 parameter를 전달하는 방식밖에 없습니다.
bind
는 함수를 실행하지 않고, this를 바꾸고 난 뒤의 함수인bound
함수를 리턴합니다.
콜백 함수는 다른 함수의 인자로 넘겨지는 함수를 의미합니다.
콜백 함수는 비동기 프로그래밍에 자주 사용되며, 자바스크립트에서 이벤트 정의를 위해 사용되기도 합니다.
콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속으로 중첩하여 사용할 때 발생하는 문제입니다.
일반적으로 콜백 지옥을 해결하는 방법에는 Promise
나 Async
가 있습니다.
Promise
는 비동기 연산이 종료된 이후에 결과를 알기 위해 사용하는 객체입니다.
Promise
의 .then
을 사용하여 함수 실행 순서를 정할 수 있습니다.
ES8에 도입된 async
, await
를 사용하면 비동기 함수를 마치 동기적 코드인것 처럼 동작하도록 구현할 수 있습니다.
async
함수는 항상 promise
를 반환합니다.
await
는 async
함수 안에서만 작동하며, await
키워드를 쓰게 되면 해당 값이 반환 되기 전까지 기다리는 동안 async
내부 함수는 일시 중단이 됩니다.
Promise
객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.
Promise
는 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있습니다.
다만, 최종 결과를 반환하는 것이 아니고, 미래에 어떤 시점에 결과를 제공하겠다는 약속(Promise)
을 반환합니다.
Promise.all()
은 여러 프로미스의 결과를 집계할 때 사용합니다.
일반적으로 서로 연관된 비동기 작업 여러 개가 모두 이행되어야 하는 경우에 사용됩니다. 또한 입력 값으로 들어온 프로미스 중 하나라도 거부당하면 Promise.all()
은 즉시 거부합니다.
Callback을 사용하면 비동기 로직의 결과값을 처리하기 위해 Callback 안에서만 처리할 수 있고, 밖에서는 그 결과값을 알 수 없습니다.
하지만 Promise를 사용하면 비동기 로직의 결과값이 Promise 객체에 저장되기 때문에 코드 작성이 용이합니다.
Promise는 .catch()
를 통해 에러 핸들링이 가능하지만, async/await
은 try-catch()
문을 사용해야 합니다.
또한 Promise도 .then()
지옥이 발생할 수 있기 때문에 코드가 길어질 수록 async/await
을 활용한 코드가 코드 흐름을 이해하기 쉽습니다.
AJAX란 서버와 통신하기 위해 XMLHttpRequest
객체를 사용하는 것을 말하며, 가장 강력한 특징은 페이지 전체를 새로고침하지 않고서도 수행되는 비동기성입니다.
.onreadystatechange
를 이용하여 서버 응답에 따른 로직을 작성할 수 있고, .open()
과 .send()
를 이용하여 서버로 요청을 보낼 수 있습니다.
var
는 let
, const
와 달리 블록 스코프가 존재하지 않습니다. 함수 스코프이거나 전역 스코프입니다. 따라서 함수가 아닌 블록 안에서 var
변수를 정의하면 해당 블록 밖에서도 이 변수에 접근할 수 있습니다.
또한 var
는 중복선언이 가능합니다. 하지만 두번째 선언은 무시되며, 에러는 발생하지 않습니다.
반대로 let
, const
는 중복선언이 불가능하며 에러가 발생합니다.
let
과 const
는 불변성의 차이를 갖습니다. const
는 변수가 아닌 상수를 정의하며, 상수를 변경하려고 하면 에러가 발생합니다.
ES6의 let
과 const
는 변수를 블록의 상단으로 호이스팅하지만 초기화하지는 않습니다.
변수가 선언되기 전에 블록 안에서 사용할 수 없는데, 이 위치를 시간상 사각지대(Temporal Dead Zone, TDZ)라고 표현합니다.
즉, 변수 스코프의 맨 위에서 변수 초기화 전까지의 영역을 의미합니다.
이 TDZ에 영향을 받는 구문으로는 let
, const
외에도 class
, contructor()
내부의 super()
가 있습니다.
constructor()
안에서 super()
를 호출하기 전까지는 this
를 사용할 수 없습니다.
함수 선언문은 function add(x, y) {}
의 형태로 쓰여지며, 완료시 undefined
가 출력됩니다.
이때, 함수 이름을 생략할 수 없습니다.
함수 표현식은 const add = function(x,y) {}
의 형태로 쓸 수 있으며, 함수 리터럴의 함수 이름을 생략할 수 있습니다.
또한 함수 표현식으로 함수를 정의하면 변수 호이스팅이 발생하기 때문에 표현식 이전에 호출할 수 없지만, 함수 선언문으로 정의하면 함수 선언문 이전에 호출할 수 있습니다.
이벤트 흐름은 캡처링 단계, 타깃 단계, 버블링 단계로 나누어집니다.
이벤트 캡처링은 최상위 조상에서 시작해 해당 요소까지 이벤트가 전파되는 과정을 의미합니다. 이 단계에서 이벤트를 잡아내려면 addEventListener
의 capture
옵션을 true
로 설정해야 합니다.
캡처링 단계 이후, 버블링이 발생합니다.
이벤트 버블링은 가장 최상단의 조상 요소를 만날 때까지 이어서 부모 요소에게 이벤트가 전파되는 과정을 의미합니다.
focus
이벤트와 같은 몇몇 이벤트를 제외하면 거의 모든 이벤트가 버블링됩니다.
이벤트 위임은 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용합니다. 이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있습니다.
이벤트 위임을 사용하려면 이벤트가 반드시 버블링되어야 합니다.
이벤트가 발생한 요소를 특정하기 위해 event.target
을 사용할 수 있습니다.
우선 컨테이너에 하나의 이벤트 핸들러를 추가합니다.
그 후 컨테이너 내의 요소에서 이벤트가 발생하면 해당 이벤트는 이벤트 버블링 단계를 통해 부모요소로 전파됩니다.
만약 이벤트 핸들러가 추가된 컨테이너까지 이벤트가 전파되면 event.target
을 사용해 이벤트가 발생한 요소가 어디인지 알아내고, 원하는 요소에서 이벤트가 발생했다면 이벤트를 핸들링합니다.
호이스팅이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미합니다.
var
로 선언한 변수의 경우 호이스팅시 ndefined
로 변수를 초기화하지만, let
과 const
로 선언한 변수의 경우 호이스팅 시 변수를 초기화하지 않습니다.
이와 비슷한 동작으로 함수 선언은 그 선언을 둘러싼 함수의 최상부나 전역 범위로 끌어올려집니다. 그러나 함수 표현식은 끌어올려지지 않습니다.
스코프는 식별자의 유효범위입니다.
식별자는 자신이 어디에서 선언됐는지에 의해 다른 코드가 자신을 참조할 수 있는 범위를 갖습니다.
var
은 가장 가까운 함수를 스코프로 갖는 반면 let
과 const
는 가장 가까운 블록을 유효 범위로 갖습니다.
자바스크립트 엔진은 식별자를 찾을 때, 자신이 속한 스코프에서 찾고, 그 스코프에 식별자가 없으면 상위 스코프에서 다시 찾아나갑니다.
이러한 현상을 스코프 체인이라고 하며, 스코프가 중첩되어있는 모든 상황에서 발생합니다.
또한 함수가 어디서 호출되었는지가 아닌, 어디서 선언되었는지를 따라서 상위 스코프를 결정합니다.
클로저는 반환된 내부 함수가 자신이 선언되었을 때의 환경 (렉시컬 환경)인 스코프를 기억하여, 그 밖에서 호출되어도 해당 환경에 접근할 수 있는 함수를 의미합니다.
이를 통해 전역 변수를 사용하지 않고도 함수 밖에서 해당 변수에 접근할 수 있는 방법을 만들어 주며, 이는 반환된 함수를 제외하면 외부에서 접근할 수 없으므로 마치 private
변수처럼 사용할 수 있습니다.
실행컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
자바스크립트는 이러한 환경 정보들을 모은 실행 컨텍스트를 콘스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장합니다.
렉시컬 환경은 두 부분으로 구성됩니다.
this
값과 같은 기타 정보도 여기에 저장됩니다.환경 레코드는 변수나 함수에 대한 정보를 갖고 있으며, 변수가 변경되면 환경 레코드의 프로퍼티가 변경됩니다.
외부 렉시컬 환경에 대한 참조는 스코프 체인에 활용됩니다.
내부 렉시컬 환경에서 원하는 변수를 찾지 못하면 내부 렉시컬 환경이 참조하는 외부 렉시컬 환경으로 검색 범위를 확장하며, 이는 전역 렉시컬 환경까지 반복됩니다.
함수와 연산자에 전달되는 값은 대부분 적절한 자료형으로 자동 변환되며, 이를 형 변환이라고 합니다.
이는 원시값이 아닌 객체간에도 동작합니다.
Symbol.ToPrimitive
를 사용하면 객체를 모든 종류로 형변환할 수 있습니다.
또한 toString()
이나 valueof()
를 사용해서도 객체를 정해진 몇몇 타입으로 변환할 수 있습니다.
자바스크립트는 런타임 시 변수의 타입이 결정됩니다.
즉, 소스가 빌드될 때 자료형이 결정되는 것이 아닌 실행 시 결정됩니다. 이를 통해 런타임까지 타입에 대한 결정을 끌고 갈 수 있지만, 실행 도중 변수에 예상치 못한 타입이 들어와 TypeError가 발생할 수 있습니다.
자바스크립트의 모든 객체들은 메서드와 속성들을 상속받기 위한 템플릿으로써 프로토타입 객체를 갖습니다.
정확히 말하자면 상속되는 속성과 메서드들은 각 객체가 아니라 객체의 생성자의 prototype
이라는 속성에 정의되어있습니다.
자바스크립트에서는 객체 인스턴스와 프로토타입간에 연결이 구성되며, 이 연결을 따라 프로토타입 체인을 타고 올라가며 속성과 메서드를 탐색합니다.
얕은 복사는 객체의 참조값(주소 값)을 복사하고, 깊은 복사는 객체의 실제 값을 복사합니다.
A라는 객체를 B로 복사했다고 했을 경우, 얕은 복사를 했다면 B를 변경할 시, A도 함께 변경되게 됩니다.
반면 깊은 복사를 했다면 A와 B가 완전히 분리되어 A가 변경되지 않게 됩니다.
대표적으로 세 가지 방법이 있습니다.
Object.preventExtensions
메서드는 객체의 확장 즉, 프로퍼티 추가를 금지합니다.
Object.seal
메서드는 객체를 밀봉합니다. 객체 밀봉이란 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의 금지를 의미합니다.
즉, 읽기와 쓰기만 가능합니다.
Object.freeze
메서드는 객체를 동결합니다. 객체 동결이란 프로퍼티 추가와 삭제, 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지를 의미합니다.
즉, 읽기만 가능합니다.
하지만 이 방법들 모두 직속 프로퍼티만 변경을 방지하므로 중첩객체까지 동결하기 위해서는 재귀적으로 메서드를 호출하거나 immer와 같은 불변성 라이브러리를 사용해야 합니다.
Blocking은 직접 제어할 수 없는 작업이 끝날때까지 기다려야 하는 경우를 의미합니다. 호출된 함수에서 I/O 작업 등을 요청했을 경우 I/O 작업이 처리되기 전까지 아무일도 하지 못합니다.
Non-Blocking은 직접 제어할 수 없는 작업이 완료되기 전에 제어권을 넘겨주는 경우를 말합니다. 호출된 함수에서 I/O 작업 등을 요청했을 경우 I/O 작업의 처리 여부와 관계없이 바로 다음 작업을 할 수 있습니다.
동기는 순차적으로 태스크를 수행하며, 요청을 보냈다면, 응답을 받아야 다음 동작이 이루어진다. 순차적으로 실행되므로, 어떤 작업이 수행중이라면 뒤의 작업은 대기합니다.
반면 비동기는 병렬적으로 태스크를 수행합니다. 현재 작업의 종료여부와는 무관하게 다음작업을 실행하므로 동기 방식과는 달리 완료순서가 보장되지 않습니다.
자바스크립트는 기본적으로, 싱글 스레드 기반으로 동기적으로 작동합니다.
undefined
는 값이 할당되지 않았음을 의미합니다. 변수가 초기화 단계를 거치면 undefined
로 남아있게 됩니다.
undeclared
는 변수 선언조차 되어있지 않은 상태를 의미합니다.
null
은 의도적으로 null
이라는 빈 값을 할당한 경우를 의미합니다.
이때, setTimeout
이라는 Web API가 A라는 함수에 있다고 가정합시다.
그리고 setTimeout
은 B라는 콜백을 전달받았다고 합시다.
A 함수를 호출하면 콜 스택에 함수가 추가됩니다. 그리고 A 내부의 setTimeout
함수도 콜 스택에 추가됩니다.
setTimeout
안에 있는 B 콜백은 Web API로 넘어가고, 그 동안 setTimeout
함수는 콜 스택에서 제거됩니다. A 함수도 콜 스택에서 제거됩니다.
Web API에서 정해진 시간동안 타이머가 돌고, B함수는 콜 스택으로 들어가는 것이 아닌 테스크 큐라는 곳으로 전달됩니다.
이벤트루프는 콜 스택과 태스크 큐를 계속 확인합니다. tick
단위로 둘을 계속 순회하며 지켜보다가 콜 스택이 비게 된다면 태스크 큐에서 해당 콜백을 빼서 콜 스택으로 넣어줍니다.
만약 Promise
를 사용한다면, 마이크로 태스크 큐를 사용합니다. 마이크로 태스크 큐는 태스크 큐보다 우선순위가 높은 큐로, 태스크 큐에 대기중인 함수가 있더라도, 마이크로 태스크 큐가 비어있지 않다면, 마이크로 태스크 큐에 대기중인 함수부터 콜스택으로 전달합니다.
마이크로 태스크 큐에는 Promise
, Observer API
, Node.js
의 process.nextTick
등이 들어갑니다.
태스크 큐에는 setTimeout
이나 setInterval
같은 함수가 들어가게 됩니다.
requestAnimationFrame
에 대해 설명해주세요.requestAnimationFrame()
은 자바스크립트가 프레임 시작 시 실행되도록 보장해주기 때문에, 밀림 현상이 발생하지 않아 부드러운 애니메이션을 제공하는 함수입니다.
이는 일반적인 비동기 task로 분류되지만, Animation frames
라는 별개의 큐에서 처리하게 됩니다.
따라서 별도의 큐에 적재된 뒤, 이벤트 루프에 의해 꺼내지기 때문에 실행이 뒤쳐지는 현상을 감소시킬 수 있습니다.
map
과 forEach
, reduce
에 대해 설명해주세요.Array.prototype.map()
은 배열 내의 모든 요소 각각에 대하여 주어진 콜백을 실행한 뒤, 그 결과값들을 모아 새로운 배열을 만들어냅니다.
Array.prototype.reduce()
는 배열의 각 요소에 대해 주어진 함수를 실행하고 하나의 결과값을 반환합니다. 초기값을 설정하여 특정 인덱스부터 실행할 수도 있습니다.
Array.prototype.forEach
는 주어진 콜백을 각각의 array
요소들에게 실행합니다.
성능은 대략 reduce
> forEach
> map
순서입니다.
자바스크립트는 가비지 컬렉션이라는 메모리 관리 방법을 사용합니다.
대표적인 가비지 컬렉션 알고리즘으로 Mark and sweep 알고리즘이 있으며, 현재 모든 최신 엔진은 이 알고리즘을 사용한 가비지 컬렉션을 제공합니다.
이 알고리즘은 더 이상 필요 없는 오브젝트를 닿을 수 없는 오브젝트로 정의합니다.
이 알고리즘은 roots
라는 오브젝트의 집합을 가지고 있으며, 주기적으로 가비지 컬렉터는 roots
부터 시작하여, roots
가 참조하는 오브젝트들, roots
가 참조하는 오브젝트가 참조하는 오브젝트들 등을 찾습니다.
이를 통해 모든 닿을 수 있는 오브젝트를 찾고 닿을 수 없는 모든 오브젝트들의 메모리를 해제합니다.
이 가비지 컬렉션은 수동으로 조작할 수 없습니다.
자바스크립트에서 클래스는 함수의 한 종류입니다.
혹자는 자바스크립트의 클래스를 문법 설탕에 불과하다고 말하지만 함수와는 중요한 차이가 몇 가지 있습니다.
class
로 만든 함수에는 특수 내부 프로퍼티인 [[IsClassConstructor]]: true
가 붙습니다. 따라서 new
와 함께 호출하지 않으면 에러가 발생합니다.
또한 클래스에 정의된 메서드는 열거할 수 없으며, 항상 strict mode
로 실행됩니다.
즉시 실행함수는 정의되자마자 즉시 실행되는 함수를 말하며, 소괄호로 함수를 감싸서 실행하는 문법을 사용합니다.
이를 통해 필요없는 전역 변수의 생성을 줄일 수 있고, 자체적인 스코프를 가지게 되기 때문에 private한 변수를 만들 수 있습니다.
엄격 모드는 "use strict";
를 스크립트 최상단에 작성하여 사용합니다.
이는 ES5에서 기존 스펙을 변경하며 하위 호환성 문제가 발생했기 때문에 도입된 문법으로, 엄격 모드를 활성화 했을 때에만 변경사항이 활성화되도록 해두었습니다.
또한 코드를 클래스와 모듈을 사용해 구성한다면 use strict
를 사용하지 않아도 자동으로 엄 격모드가 활성화됩니다.
콜 스택은 코드가 실행되면서 생성되는 실행 컨텍스트를 저장하는 자료구조입니다. 이를 통해 변수 식별자, 스코프 체인 및 this 관리, 코드 실행 순서 관리등을 수행하게 됩니다.
또한 콜 스택은 원시 타입의 값과 참조 타입의 메모리 힙 주소값이 저장되는 공간입니다.
메모리 힙은 원시 타입이 아닌 타입의 데이터가 저장되는 공간으로, 메모리 할당이 일어나게 됩니다.
Spread 연산자를 사용하면 객체 혹은 배열을 펼칠 수 있습니다. 이를 사용해 객체의 프로퍼티들을 기존 객체를 건드리지 않으면서 다른 객체에 모두 집어 넣을 수 있습니다.
객체나 배열의 이름 앞에 ...
을 붙여 사용합니다.
Rest 연산자는 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능합니다.
객체나 배열에서 Rest 연산자를 사용하면 디스트럭처링된 값들을 다시 객체나 배열로 묶을 수 있습니다.
함수의 파라미터에서는 파라미터가 몇개가 될지 모르는 상황에서 이 파라미터들을 배열로 묶어주는 역할을 수행합니다.
새로 만들어줄 객체나 배열의 이름 앞에 ...
을 붙여 사용합니다.
제너레이터는 function*
로 만들어지는 제너레이터 함수를 통해 만들 수 있으며, 해당 함수는 본문을 실행하지 않고 제너레이터 객체를 반환합니다.
그 후, next()
를 수행할 때마다, 함수에서 yield
한 값들을 하나씩 반환합니다.
next()
를 사용할 수 있는 것으로 짐작할 수 있듯, 제너레이터 객체는 이터레이터이자 이터러블입니다.
따라서 제너레이터에서도 for ... of
나 Spread 문법, 배열 디스트럭처링과 같은 기능을 사용할 수 있습니다.
이터레이션 프로토콜은 순회 가능한 데이터 컬렉션을 만들기 위해 ES6에서 도입된 규칙입니다.
이터레이션 프로토콜에는 이터러블 프로토콜과 이터레이터 프로토콜이 있습니다.
Symbol.iterator
를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다.
이러한 규약을 이터러블 프로토콜이라 하며, 이터러블 프로토콜을 준수한 객체를 이터러블이라 합니다.
이터러블의 Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다. 이터레이터는 next
메서드를 소유하며 next
메서드를 호출하면 이터러블을 순회하며 value
와 done
프로퍼티를 갖는 이터레이터 리절트 객체를 반환합니다.
이러한 규약을 이터레이터 프로토콜이라 하며, 이터레이터 프로토콜을 준수한 객체를 이터레이터라 합니다.
면접 앞두고 있었는데, 좋은 글 작성해주셔서 감사드립니다.