본문 바로가기
Developer/취업 | 취준

면접대비 질문 정리: 자바스크립트, 타입스크립트

by 청량리 물냉면 2024. 5. 1.
반응형

면접질문 참고

 

1. 객체지향이란?

 답변

객체지향 프로그래밍이란 여러 개의 독립적 단위, 즉 객체의 집합으로 프로그래밍하는 것을 뜻합니다.

생성자 함수를 통해 객체를 생성할 수 있고 생성자 함수 내에 속성(property)과 행동(method)를 지정하면 생성된 객체들은 공통된 속성과 행동을 사용할 수 있습니다.

모듈화, 은닉화, 상속이 가능하다는 특징이 있습니다. 

 

참고: 

 

2. var, let, const의 차이 및 특징

 답변

var 선언은 전역 스코프 또는 함수 스코프이며, let과 const는 블록 스코프입니다.

var 변수는 범위 내에서 업데이트 및 재선언할 수 있습니다. let 변수는 업데이트할 수 있지만, 재선언은 할 수 없습니다. const 변수는 업데이트와 재선언 둘 다 불가능합니다.

세 가지 모두 최상위로 호이스팅될 수 있지만 var 변수는 undefined(정의되지 않음)으로 초기화되는 반면 let과 const 변수는 초기화되지 않습니다.

var와 let은 초기화하지 않은 상태에서 선언만 하는 것이 가능하지만, const는 선언과 동시에 초기화를 반드시 해주어야 합니다.

 

참고: https://www.freecodecamp.org/korean/news/var-let-constyi-caijeomeun/

 

 

3. 스코프(Scope)와 스코프 체인(Scope Chain)

 답변

‘변수에 접근할 수 있는 범위’를 뜻합니다.

자바스크립트의 스코프는 크게 전역 스코프, 지역 스코프로 나뉩니다.

전역 스코프의 경우 최상위 레벨에 해당하는 스코프이기 때문에 어디에서든 접근이 가능합니다.

반면 지역 스코프의 경우 특정 함수에 해당하는 스코프로, 해당 함수 자신과 하위 함수에서만 자원에 접근할 수 있습니다.

 

스코프 체인

내부함수는 호출된 변수를 먼저 자신의 스코프에서 찾고, 없으면 한 단계씩 외부(상위) 스코프로 나가면서 찾게 됩니다. 이를 스코프 체인이라고 합니다.

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  innerFunc();
}

outerFunc(); // 10

내부함수 innerFunc가 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있는 것, 다시 말해 상위 스코프에 접근할 수 있는 것은 렉시컬 스코프의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 스코프 체인(스코프가 어떻게 연결되고 있는지 보여주는 일종의 리스트)을 자바스크립트 엔진이 검색하였기에 가능한 것입니다. 좀더 자세히 설명하면 아래와 같습니다.

 

1. innerFunc 함수 스코프(함수 자신의 스코프를 가리키는 활성 객체) 내에서 변수 x를 검색하지만, 검색에 실패하였습니다. => 상위 스코프로 이동
2. innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프(함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체)에서 변수 x를 검색한다. 검색이 성공하였습니다. (만약 outerFunc에서도 x를 찾지 못했다면, outerFunc의 상위 스코프인 전역 스코프에서 x를 찾게 됩니다.)

 

 

4. 호이스팅(hoisting)이란?

 답변

호이스팅이란 변수함수 선언문이 스코프 내의 최상단으로 끌어올려지는 현상을 말합니다.

(선언문만 호이스팅되며, 대입문은 호이스팅되지 않습니다.)

 

변수 호이스팅

  • var 키워드로 선언된 변수는 호이스팅 시 선언과 동시에 undefined로 초기화됩니다.
  • let과 const 키워드로 선언된 변수는 선언은 호이스팅되지만, 초기화는 실제 코드에서 해당 변수에 도달했을 때 이루어집니다. 이 과정에서 초기화 전까지의 구간을 TDZ(Temporal Dead Zone, 일시적 사각지대)라고 합니다. TDZ 구간에서는 해당 변수에 접근하면 참조 에러가 발생합니다.

함수 호이스팅

  • 함수 선언문(Function Declaration)은 호이스팅이 발생하며, 함수 선언문 이전에 함수를 호출해도 정상적으로 실행됩니다.
  • 함수 표현식(Function Expression)은 호이스팅이 이뤄지지 않기 때문에 표현식 이전에 함수를 호출하면 참조 에러가 발생합니다.

참고: 

 

5. ES6에 추가된 문법

 답변

  • const, let
  • arrow function
  • template literals(탬플릿 리터럴) - 백틱사용하여 문자열 내에서 변수사용가능합니다.
  • 구조분해할당
  • import export
  • promise
  • spread 연산자

 

6. 동기와 비동기

✅ 답변

동기는 요청을 보낸 후 기다렸다가 해당 응답을 받고 나서 다음 동작을 실행하는 것을 의미하고, 비동기는 요청을 보낸 후 응답에 관계없이 순차적으로 다음 코드를 먼저 실행하는 것입니다.

 

 

7. 렉시컬 환경 (Lexical Environment)

✅ 답변

Lexical Environment는 코드 block, function, script를 실행하기 앞서 생성되는 특별한 객체로, 실행할 스코프 범위 안에 있는 변수와 함수를 프로퍼티로 저장하는 객체입니다.
소스 코드를 실행하면서 참조가 필요한 변수의 값이 있다면, 이 Lexical Environment라는 객체에서 식별자 이름을 키로 찾게 됩니다.

 

참고: https://developer-alle.tistory.com/407

 

 

8. 실행 컨텍스트 (Execution context)

 답변

실행 컨텍스트는 실행하고 있는 함수를 트래킹하기 위한 특별한 자료 구조입니다. 

현재 실행하고 있는 함수 내의 현재 변수 상태와 this의 값 등을 저장하고 있고, 현재 실행 중인 line을 기억하고 있습니다.

함수가 호출될 때마다 새로운 실행 컨텍스트가 생성되며, 함수의 실행이 끝나면 해당 컨텍스트는 스택에서 제거됩니다. 따라서 중첩된 함수가 호출될 때마다 새로운 실행 컨텍스트가 생성되고, 각 함수는 자신만의 변수 환경과 this 값을 유지합니다. 이러한 구조를 통해 중첩된 함수의 실행이 끝나면 다시 이전 실행 컨텍스트로 돌아갈 수 있습니다.

 

참고: https://developer-alle.tistory.com/407

 

 

9. 클로저 (closure) ⭐

 답변

클로저의 정의

  • 클로저란, 함수와 그 함수의 렉시컬 환경의 조합입니다.
  • 클로저는 함수가 생성될 당시의 외부 변수를 기억하고, 생성된 이후에도 그 변수에 계속해서 접근이 가능한 기능입니다. 외부 함수의 기능이 끝나서 외부 함수가 소멸된 이후에도, 내부 함수는 외부 변수의 변수에 접근할 수 있습니다. 
  • 즉, 클로저는 내부 함수에서 외부 함수의 범위에 대한 접근을 제공합니다.
function outer() {
  let i = 0;
  function inner() {
    return i++;
  }
  return inner;
}

let innerFn = outer();

console.log(innerFn()); // 0
console.log(innerFn()); // 1
console.log(innerFn()); // 2
console.log(innerFn()); // 3
console.log(innerFn()); // 4
console.log(innerFn()); // 5

innerFn = null; // 클로저 참조 해제

위 예제에서 outer 함수가 호출되면 `i` 변수가 0으로 초기화됩니다.
outer 함수 inner 함수를 반환하고, innerFn 변수에 할당됩니다.

 

클로저의 동작 원리

  • outer 함수가 실행된 후에도 innerFn은 outer 함수의 스코프에 접근할 수 있습니다. 이는 inner 함수가 클로저로서 outer 함수의 렉시컬 환경을 기억하고 있기 때문입니다.
  • 따라서 innerFn이 호출될 때마다 i 변수는 계속 증가합니다.

실행 컨텍스트와 클로저

  • outer 함수가 반환된 후에도 i 변수는 사라지지 않습니다. inner 함수가 i 변수를 참조하고 있기 때문에 outer 함수의 렉시컬 환경은 여전히 메모리에 유지됩니다. 이를 클로저라고 합니다.
  • 클로저는 함수가 선언된 환경을 기억하여 외부 함수의 변수를 계속 참조할 수 있습니다. 이는 outer 함수의 실행 컨텍스트가 완전히 종료되지 않고, inner 함수의 참조가 유지되기 때문입니다.

클로저의 메모리 관리

  • 클로저는 강력한 기능이지만, 사용이 끝난 클로저를 제대로 관리하지 않으면 메모리 누수가 발생할 수 있습니다. 예제에서 innerFn을 더 이상 사용하지 않는다면, 이를 null로 초기화하여 참조를 해제할 수 있습니다.

글로벌 실행 컨텍스트

  • outer() 실행 컨텍스트 내부 활성 객체: `i = 0`, `funtion inner(){ }`
  • 참조 중인 실행 컨텍스트가 존재하기 때문에, i 값은 임시 저장공간에 들어갑니다. (outer 함수는 완전한 종료 상태에 이르지 못합니다.)

클로저 사용 이유

  • 변수 은닉화: 클로저를 사용하면 함수 내부의 변수를 외부에서 직접 접근할 수 없도록 은닉할 수 있습니다.
  • 상태 유지: 클로저는 함수 호출 간에 상태를 유지할 수 있게 해줍니다. 위 예제에서 i 변수는 inner 함수 호출 간에 값이 유지됩니다.
더보기
  • 외부 함수의 실행이 끝나서 외부 함수가 소멸된 이후에도 외부함수 실행 컨텍스트 내의 활성 객체(Activation object. 변수, 함수 선언 등의 정보를 가지고 있다)는 내부함수에 의해 참조되는 한 유효합니다. 그렇기 때문에 내부 함수는 외부 함수의 변수에 계속해서 접근할 수 있습니다.
  • (클로저는 함수가 생성될 때의 렉시컬 환경을 기억하고, 이 환경을 나중에 호출될 때 사용합니다. 이를 통해 중첩 함수가 외부 함수의 변수 및 함수에 접근할 수 있게 됩니다.)

 

참고:

 

10. Promise와 콜백의 차이점 

 답변

Promise와 콜백은 JavaScript에서 비동기 작업을 처리하기 위해 사용되는 주요한 방법입니다.

먼저, 콜백 함수는 전달인자로 다른 함수에 전달되는 함수를 의미합니다. 콜백 함수는 비동기 작업이 완료되었을 때 호출되는 함수이기 때문에, 순서가 보장되지 않는 비동기 함수의 실행순서를 보장하기 위해 콜백을 이용할 수 있습니다. 하지만 콜백이 중첩되면 코드의 길이가 길어지고 깊이가 깊어지면서 코드의 가독성이 매우 떨어지게 됩니다. 이를 콜백 지옥이라고 부릅니다.

// 콜백 지옥
doSomething((result1) => {
  doSomethingElse(result1, (result2) => {
    doAnotherThing(result2, (result3) => {
      // ...중첩된 콜백
    });
  });
});

 

이를 해결하기 위해 등장한 문법이 바로 ES6의 promise입니다.

promise란 비동기 연산의 상태를 나타내기 위한 객체로 pending, fulfilled, reject 총 3가지의 상태를 가질 수 있습니다.

  • `pending`: 대기, 코드를 실행할 때 처음 값은 무조건 대기 상태
  • `fulfilled`: 이행, `resolve()` 함수 호출하면 이행 상태가 됨, Promise { <fulfilled> }
  • `reject`: 거부. `reject()` 함수 호출하면 거부 상태가 됨
const promise = new Promise((resolve, reject) => {
  console.log("promise...");
});

console.log(promise); //Promise { <pending> }

Promise는 체이닝(chaining)을 이용해 콜백 지옥에서 발생하는 가독성을 개선해줍니다.

계층적으로 깊어지는 형식인 콜백 함수와 달리, Promise는 `.then()`, `.catch()`, `.finally()` 메서드를 사용하여 비동기 작업을 연속적으로 처리할 수 있으며, 이로 인해 코드가 수평적으로 펼쳐지게 됩니다.

// Promise 체이닝
doSomething()
  .then(result1 => doSomethingElse(result1))
  .then(result2 => doAnotherThing(result2))
  .catch(error => handleError(error)); // 에러 처리도 간단히 가능

이처럼 Promise는 비동기 작업의 순차적 실행을 더 직관적이고 읽기 쉽게 만들어 주고, 에러 처리도 한 곳에서 관리할 수 있기 때문에 콜백 지옥 문제를 해결할 수 있습니다.

 

참고: 

 

11. async, await

 답변

(async, await은 promise.then보다 좀 더 세련되게 promise의 result 값을 얻을 수 있도록 해주는 문법입니다. promise.then보다 가독성이 좋고 코드를 작성하기도 쉽습니다.)

function 앞에 async를 붙이면 해당 함수는 항상 promise를 반환합니다. promise가 아닌 값을 반환하더라도 이행 상태의 promise(resolved promise)로 값을 감싸 이행된 promise가 반환되도록 합니다.

`Pending(대기)` : 비동기 처리 로직이 아직 완료되지 않은 상태
`Fulfilled(이행)` : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태
`Rejected(실패) `: 비동기 처리가 실패하거나 오류가 발생한 상태

 

await는 async 함수 안에서만 동작하는 키워드입니다.

자바스크립트는 await 키워드를 만나면 promise가 처리될 때까지 기다리고, 결과는 그 이후에 반환됩니다.

promise가 처리될 때까지 함수 실행을 기다리게 만들고 promise가 처리되면 그 결과와 함께 실행이 재개됩니다.

promise가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않습니다

 

참고:

 

12. spread 연산자

 답변

기존 배열이나 객체의 전체 또는 일부를 다른 배열이나 객체로 빠르게 복사할 수 있는 연산자입니다.

 

 

13. this

 답변

자바스크립트에서 this는 현재 함수를 부른 객체가 누구인지를 나타냅니다. 즉 this는 고정값이 아닙니다.

  • global context에서 this는 전역 객체(window)를 가리킵니다. 
  • 일반 함수에서 this는 전역 객체(window), 엄격 모드에서는 undefined를 가리킵니다. 
  • 객체의 메서드 내부에서 this는 해당 메서드를 소유한 객체를 가리킵니다. 
  • 생성자 함수에서 this는 새로 생성된 객체를 가리킵니다. 
  • call, apply, bind를 사용하여 this를 명시적으로 설정할 수 있습니다.

call과 apply: 첫 번째 인자로 this를 지정하고, 추가 인자로 함수의 인수를 전달합니다.

function showThis() {
  console.log(this);
}

const obj = { value: 42 };
showThis.call(obj); // { value: 42 }
showThis.apply(obj); // { value: 42 }

bind: 새로운 함수를 반환하고, 반환된 함수의 this는 지정된 객체로 설정됩니다.

const boundFunction = showThis.bind(obj);
boundFunction(); // { value: 42 }

 

화살표 함수는 자신만의 this 바인딩을 가지지 않습니다. 대신, 화살표 함수는 자신을 포함하는 외부 함수나 전역 스코프에서 this를 상속받습니다.

 

참고:

 

14. call, apply, bind에 대해 자세히 설명

 답변

JavaScript의 call, apply, bind 메서드는 모두 함수의 this 값을 설정하고 함수를 호출하는 데 사용됩니다. 하지만 각각의 사용 방법과 목적이 조금씩 다릅니다.

먼저 call 메서드는 함수를 호출하면서 첫 번째 인수로 this 값을 설정하고, 그 이후의 인수들을 함수의 매개변수로 전달합니다.

functionName.call(thisArg, arg1, arg2, ...);
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Alice' };

greet.call(person, 'Hello', '!');

위 예제에서 greet 함수는 person 객체를 this로 사용하여 호출됩니다. 결과적으로 출력은 Hello, Alice!가 됩니다.

 

apply 메서드는 call과 거의 동일하지만, 인수를 배열로 전달한다는 점에서 차이가 있습니다. 이는 인수의 수가 동적으로 변할 때 유용합니다.

functionName.apply(thisArg, [arg1, arg2, ...]);
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Bob' };

greet.apply(person, ['Hi', '?']);

위 예제에서 greet 함수는 person 객체를 this로 사용하여 호출됩니다. 결과적으로 출력은 Hi, Bob?가 됩니다.

 

 

bind 메서드함수를 호출하지 않고 this 값과 인수를 바인딩하여 새로운 함수를 반환합니다. 이 새로운 함수는 나중에 호출될 수 있습니다.

const boundFunction = functionName.bind(thisArg, arg1, arg2, ...);
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Charlie' };

const greetPerson = greet.bind(person, 'Hey', '...');
greetPerson();  // "Hey, Charlie..."

위 예제에서 greet 함수는 person 객체를 this로 사용하여 greetPerson이라는 새로운 함수로 바인딩됩니다. 나중에 greetPerson을 호출하면 바인딩된 this 값과 인수들이 사용됩니다.

 

추가 예제: call, apply, bind

const obj = { value: 42 };

function printValue(arg1, arg2) {
  console.log(this.value, arg1, arg2);
}

// call: 개별 인수로 전달
printValue.call(obj, 'firstArg', 'secondArg'); // 42 'firstArg' 'secondArg'

// apply: 배열로 인수 전달
printValue.apply(obj, ['firstArg', 'secondArg']); // 42 'firstArg' 'secondArg'

// bind: 새로운 함수 반환
const boundPrintValue = printValue.bind(obj, 'firstArg', 'secondArg');
boundPrintValue(); // 42 'firstArg' 'secondArg'

이 예제에서는 printValue 함수가 obj를 this로 사용하여 호출됩니다. call과 apply는 즉시 호출되며, bind는 새로운 함수를 반환하여 나중에 호출할 수 있게 합니다.

 

 

15.  이벤트 루프

 답변

이벤트 루프(Event Loop)는 자바스크립트의 비동기 처리를 가능하게 하는 메커니즘으로, 싱글 스레드로 동작하는 자바스크립트가 동시에 여러 작업을 처리할 수 있도록 돕습니다.

 

자바스크립트의 동작 원리

  1. 콜 스택(Call Stack): 자바스크립트는 하나의 콜 스택을 가지고 있습니다. 콜 스택은 실행할 함수들이 차례로 쌓이고, 실행이 완료되면 스택에서 제거되는 구조입니다. 자바스크립트의 모든 함수 호출은 이 콜 스택에서 이루어집니다.
  2. 비동기 작업 큐(Task Queue / Callback Queue): 비동기 작업이 완료되면 콜백 함수가 이 큐에 들어갑니다. 타이머 함수(setTimeout, setInterval), 네트워크 요청, DOM 이벤트 등이 비동기 작업의 예입니다.
  3. 이벤트 루프(Event Loop): 이벤트 루프는 콜 스택이 비어 있는지 확인하고, 비어 있을 때 비동기 작업 큐에서 콜백 함수를 가져와 콜 스택에 넣어 실행하는 역할을 합니다. 이 과정이 반복되면서 비동기 작업들이 처리됩니다.

이벤트 루프의 동작 방식

  1. 콜 스택이 비어 있는지 확인: 이벤트 루프는 지속적으로 콜 스택을 감시합니다. 콜 스택이 비어 있을 때만 비동기 작업 큐에서 작업을 가져와 실행합니다.
  2. 비동기 작업 큐에서 콜백 함수 가져오기: 콜 스택이 비어 있으면, 비동기 작업 큐에서 대기 중인 콜백 함수나 작업을 콜 스택으로 이동시킵니다.
  3. 콜백 함수 실행: 콜 스택에서 콜백 함수가 실행되고, 완료되면 콜 스택에서 제거됩니다. 이후 다시 콜 스택이 비어 있는지 확인하고, 이 과정이 반복됩니다.

참고:

 

16. Debounce(디바운스)와 Ttrottle(쓰로틀)에 대해 설명

✅ 답변

Debounce(디바운스)와 Tthottle(쓰로틀)은 일정 시간동안 동일한 이벤트가 여러 번 발생했을 때 이벤트 핸들링 횟수를 1번으로 제한하는 기술을 뜻합니다.

디바운스 같은 경우에는 마지막으로 발생한 이벤트만 처리하고 쓰로틀은 처음 발생한 이벤트만 처리한다는 차이점이 존재합니다.


참고:

 

17. 이벤트 전파(이벤트 버블링)

✅ 답변

HTML 문서의 각 엘리먼트들은 계층적인 구조를 가지고 있습니다. 이러한 특징 때문에 HTML DOM 요소에 이벤트가 발생할 경우 연쇄적 이벤트 흐름이 일어나게 됩니다.

이러한 현상을 이벤트 전파(Event Propagation)라 부르며, 전파 방향에 따라 버블링과 캡처링으로 구분합니다.

그 중 이벤트 버블링(Bubbling)이란, 자식 요소에서 발생한 이벤트가 부모 요소로 전파되는 현상을 의미합니다.

이벤트 버블링을 막아야 하는 경우는 거의 없지만, 버블링을 반드시 막아야 하는 상황이라면(특정 요소에 이벤트 핸들러를 연결했는데, 상위 요소에도 같은 이벤트 핸들러가 연결되어 있다면 의도하지 않게 두 개의 이벤트가 발생할 수 있습니다.) 이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 버블링을 강제로 중단시킬 수 있습니다.

 

참고:

 

18. 이벤트 전파(이벤트 캡처링)

✅ 답변

이벤트 캡처링이란 버블링의 반대 개념으로, 한 요소에 이벤트가 발생하면 그 요소의 자손 요소의 이벤트도 같이 발생되는 이벤트 전파입니다.  addEventListener의 capture 옵션을 true로 설정해줌으로써 캡처링을 사용할 수 있습니다.

이벤트 캡처링의 경우 실무에서는 거의 쓰이지 않는다고 합니다.

 

참고:

 

19. 이벤트 위임

✅ 답변

이벤트 위임(Event Delegation)은 여러 개의 하위 DOM 요소에 각각 이벤트 핸들러를 등록하는 대신, 하나의 상위 DOM 요소에 이벤트 핸들러를 등록하는 방법을 말합니다.

이벤트 위임을 적용하면 최상위 요소에 하나의 이벤트 핸들러만 존재하게 되므로, 메모리 사용량이 감소한다는 이점이 있습니다. 또한 동적으로 추가되거나 제거되는 요소에도 일관성 있게 이벤트를 적용할 수 있습니다.

반면 이벤트 위임을 사용할 때의 주의점은 이벤트 위임을 사용할 때, 이벤트가 발생한 실제 요소(event.target)가 개발자가 의도한 요소가 아닐 수 있다는 것입니다. 따라서 이벤트 위임을 통해 이벤트를 처리할 때는 event.target을 사용해 실제 이벤트가 발생한 요소를 확인하고, 필요한 경우 event.currentTarget과 비교하거나 조건문을 통해 원하는 요소에서만 동작하도록 해야 합니다.

 

참고:

 

20. map과 foreach의 차이

✅ 답변

두 함수 모두 배열의 전체 요소를 순회하며 특정 작업을 수행하지만, forEach는 원본을 변경하는 반면 map은 작업을 통해 변환된 새로운 배열을 반환한다는 차이점이 있습니다.

 

 

21. `==`와 `===`의 차이

✅ 답변

`==`는 동등 연산자로, 비교 대상의 타입이 다르더라도 강제로 형변환을 진행해 비교하므로 값만 일치하다면  true를 리턴합니다.

반면 `===`는 일치 연산자로 두 비교 대상의 타입과 값이 모두 같을 경우에만 true를 리턴합니다.

 

 

22. 이벤트 등록 & 해제

✅ 답변

자바스크립트에서 이벤트를 등록할 때는 addEventListener메서드를, 이벤트를 해제할 때는 removeEventListener 메서드를 사용합니다. removeEventListener 메서드 같은 경우, 이벤트를 등록할 때 사용한 인수를 동일하게 사용해야 합니다. 예를 들어, 익명 함수를 사용하여 이벤트를 등록하면 나중에 동일한 익명 함수 참조를 제공할 수 없기 때문에 이벤트를 해제할 수 없습니다.

button.addEventListener('click', function() {
  console.log('Button clicked!');
});

// 이 익명 함수는 다른 함수 참조이므로, 이벤트를 제거할 수 없습니다.
button.removeEventListener('click', function() {
  console.log('Button clicked!');
});

 

 

23. 자바스크립트의 메모리 관리와 Garbage Collection

✅ 답변

자바스크립트의 메모리 관리는 자바스크립트 엔진에 의해 자동으로 이루어집니다.

자바스크립트 엔진은 주로 메모리 할당과 메모리 해제, 두 가지의 메모리 관리 작업을 수행합니다.

자바스크립트 엔진은 변수, 객체, 함수 등을 생성할 때 필요한 만큼의 메모리를 자동으로 할당합니다. 예를 들어 변수를 선언하거나 객체를 생성하면, 자바스크립트 엔진은 이를 위해 적절한 메모리 공간을 확보합니다.

또한 자바스크립트 엔진은 더 이상 필요하지 않은 메모리(더 이상 참조되지 않는 객체가 차지하고 있는 메모리)를 자동으로 해제해줍니다. 이 과정은 Garbage Collection(GC)이라고 불리며, 엔진은 여러 가지 GC 알고리즘을 사용하여 메모리 해제를 관리합니다. 주요 알고리즘으로는 참조 카운팅(Reference Counting)과 마크 앤 스윕(Mark-and-Sweep)이 있습니다.

 

Garbage Collection

Garbage Collection(GC)은 자바스크립트 엔진에서 자동으로 더 이상 필요하지 않은 메모리를 해제하는 과정입니다. GC가 정리하는 메모리는 주로 더 이상 참조되지 않는 객체들입니다. 아래는 GC가 정리하는 주요 메모리 유형들입니다:

  1. 변수:
    • 함수 내부에서 선언된 지역 변수는 함수가 종료되면 더 이상 참조되지 않으므로 GC의 대상이 됩니다.
    • 블록 스코프 내에서 선언된 변수(let, const)도 블록이 종료되면 참조되지 않게 됩니다.
  2. 객체: 더 이상 어떤 변수나 객체도 참조하지 않는 객체는 GC에 의해 정리됩니다. 예를 들어, 객체를 null로 설정하면 해당 객체는 더 이상 참조되지 않으므로 GC 대상이 됩니다.
  3. 배열: 배열 내부의 요소들도 더 이상 참조되지 않는 경우 GC 대상이 됩니다. 배열 자체가 더 이상 참조되지 않는 경우에도 전체 배열이 GC 대상이 됩니다.
  4. 함수: 함수 선언이 더 이상 사용되지 않으면 GC 대상이 됩니다. 클로저에 의해 참조되는 함수는 해당 클로저가 존재하는 동안 메모리에 유지됩니다.
  5. DOM 요소: 브라우저 환경에서 DOM 요소가 자바스크립트 객체에 의해 참조되지 않는 경우, 해당 요소도 GC에 의해 정리됩니다.
  6. 클로저: 클로저는 함수와 해당 함수가 선언된 Lexical Environment의 조합을 말합니다. 클로저에 의해 참조되는 변수나 함수는 해당 클로저가 유효한 동안 메모리에 남아 있습니다. 클로저가 더 이상 참조되지 않으면 해당 클로저와 관련된 메모리도 GC 대상이 됩니다.

 

24. null, undefined, undeclared, NaN 에 대해 설명

✅ 답변

null

null은 의도적으로 "비어있음" 또는 "존재하지 않음"을 나타내는 값입니다. 이것은 자바스크립트에서 객체가 없다는 것을 명시적으로 표현할 때 사용됩니다.

  • 타입: object
let obj = null; // obj는 현재 객체가 아님을 나타냄

 

undefined

undefined는 변수가 선언되었지만 아직 값이 할당되지 않은 상태를 나타냅니다. 자바스크립트에서 변수가 초기화되지 않은 경우 기본값으로 undefined를 가집니다.

  • 타입: undefined
let x;
console.log(x); // undefined

 

undeclared

undeclared 변수는 선언되지 않은 변수를 참조할 때 발생하는 상태를 의미합니다. 변수가 선언되지 않았는데 접근하려고 하면 ReferenceError가 발생합니다.

  • 타입: 존재하지 않으므로 타입이 없습니다.
console.log(y); // ReferenceError: y is not defined

 

NaN (Not-a-Number)

NaN은 숫자가 아닌 값을 숫자로 변환하려고 할 때 발생하는 특수한 값입니다. 예를 들어, 숫자가 아닌 문자열을 숫자로 변환하려고 하면 NaN이 반환됩니다. NaN은 자기 자신과도 같지 않다는 특이한 성질을 가지고 있습니다.

  • 타입: number
let result = parseInt("hello"); // NaN
console.log(result); // NaN
console.log(NaN === NaN); // false

 

정리

  • null: 명시적으로 "값이 없음"을 나타냄.
  • undefined: 변수가 선언되었지만 값이 할당되지 않음.
  • undeclared: 변수가 선언되지 않음.
  • NaN: 숫자가 아닌 값을 숫자로 변환하려고 할 때 발생하는 값.

 

25. 프로토타입에 대해 설명

✅ 답변

자바스크립트에서 프로토타입은 객체지향 프로그래밍의 중요한 개념으로, 객체 간에 속성과 메서드를 공유할 수 있도록 해줍니다. 자바스크립트는 프로토타입 기반의 언어이기 때문에, 모든 객체는 자신의 프로토타입을 가질 수 있습니다.

 

기본 개념

  1. 프로토타입 체인: 객체가 어떤 속성을 찾을 때, 먼저 자신의 속성에서 찾고, 없으면 자신의 프로토타입에서 찾습니다. 이 과정은 프로토타입 체인을 따라 계속 진행됩니다. 이 체인이 끝날 때까지 속성을 찾지 못하면 undefined를 반환합니다.
  2. 프로토타입 속성: 모든 객체는  Prototype (__proto__)이라는 숨겨진 속성을 가지고 있으며, 이를 통해 프로토타입을 연결합니다. 보통 Object.getPrototypeOf(obj)를 통해 확인할 수 있습니다.
  3. 생성자 함수와 프로토타입: 생성자 함수를 정의하고 해당 생성자 함수의 prototype 속성에 메서드나 속성을 추가하면, 해당 생성자로 생성된 모든 객체는 그 메서드나 속성을 공유할 수 있습니다.

예시

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person("Alice");
const bob = new Person("Bob");

alice.greet(); // "Hello, my name is Alice"
bob.greet();   // "Hello, my name is Bob"

위의 예시에서, greet 메서드는 Person 생성자의 프로토타입에 정의되어 있어, Person의 모든 인스턴스가 이 메서드를 공유합니다.

 

프로토타입의 장점

  • 메모리 효율성: 메서드나 속성을 각 인스턴스가 아닌 프로토타입에 정의함으로써 메모리를 절약할 수 있습니다.
  • 상속: 프로토타입을 활용하면 객체 간에 속성과 메서드를 상속할 수 있습니다.

프로토타입 변경

프로토타입은 런타임에 변경할 수 있으며, 이는 객체의 동작을 동적으로 수정할 수 있는 강력한 기능을 제공합니다.

 

결론

프로토타입은 자바스크립트의 핵심 개념으로, 객체 간의 관계를 설정하고 메서드와 속성을 효율적으로 공유하는 데 사용됩니다. 프로토타입 기반의 상속과 체인은 자바스크립트의 유연성을 높여줍니다.

 

참고:

 

25. 원시값과 참조값에 대해 설명

✅ 답변

원시값(Primitive Values)단순한 데이터 유형으로, 변경할 수 없는(불변(immutable)) 값을 의미합니다.

원시값은 변수에 직접 값이 할당된다는 특징이 있습니다.

원시값의 종류는 다음과 같습니다.

  1. String: 텍스트 데이터 (예: "Hello")
  2. Number: 숫자 데이터 (예: 42, 3.14)
  3. Boolean: 논리값 (예: true, false)
  4. Undefined: 정의되지 않은 값
  5. Null: 고의적으로 비어 있는 값
  6. Symbol: 고유한 식별자 (ES6에서 도입)
  7. BigInt: 임의의 정밀도를 가지는 정수 (ES11에서 도입)

원시값의 특성

- 불변성: 원시값은 한 번 생성되면 그 값을 변경할 수 없습니다.

값 자체를 바꾸려면, 새로운 값을 변수에 다시 할당해야 합니다. 

let a = 10;
a = 20;  // 새로운 값 20을 할당

이 경우 변수 a의 값이 10에서 20으로 변경된 것이 아니라, 기존의 값은 사라지고 새로운 값 20이 a에 할당된 것입니다.

기존의 값(10)은 그대로 남아 있지만 더 이상 참조되지 않으므로 Garbage Collection에 의해 제거됩니다.

 

- 값 복사: 원시값을 다른 변수에 복사하면, 새로운 변수는 원래 값과 독립적인 값을 가지게 됩니다.

let x = 5;
let y = x;  // x의 값을 y에 복사
y = 10;     // y의 값을 변경해도 x에 영향을 주지 않음

console.log(x); // 5
console.log(y); // 10

여기서 y는 x의 값을 복사했을 뿐, 둘은 완전히 별개의 값입니다. y를 변경해도 x에는 영향을 미치지 않습니다.

 

 

참조값(Reference Values)객체, 배열, 함수와 같은 복합적인 데이터 유형입니다.

이 값들은 크기가 크거나 복잡할 수 있기 때문에 변수에 값을 직접 저장하지 않고, 값이 저장된 메모리 주소를 참조합니다.

따라서 참조값을 변수에 할당할 때, 변수에는 값이 아닌 메모리 주소(참조)가 저장됩니다.

 

참조값의 특성

- 변경 가능성: 참조값은 가변성(mutable)을 가지며, 객체의 속성이나 배열의 요소를 변경할 수 있습니다. 메모리 주소는 그대로 유지되며, 객체나 배열의 내부 데이터는 수정 가능합니다.

let obj = { name: 'Alice', age: 25 };
obj.age = 26;  // 객체의 속성 값 변경 가능

console.log(obj.age);  // 26

이 경우 객체의 age 속성은 메모리 주소는 그대로 유지된 채로 25에서 26으로 변경됩니다.

 

- 참조 복사: 참조값을 다른 변수에 할당하면, 값 자체가 아닌 메모리 주소가 복사됩니다. 따라서 복사된 변수를 통해 값을 변경하면 원래 값에도 영향을 미칩니다.

let obj1 = { name: 'Bob', age: 30 };
let obj2 = obj1;  // obj1의 메모리 주소를 obj2에 복사

obj2.age = 31;    // obj2를 통해 age를 변경

console.log(obj1.age);  // 31 (obj1도 변경됨)

이 경우, obj1과 obj2는 같은 객체를 참조하고 있기 때문에 obj2를 통해 속성을 변경하면 obj1에도 동일하게 반영됩니다.

 

 

원시값과 참조값의 비교

원시값 (Primitive Value) 참조값 (Reference Value)
값 저장 방식 값 자체가 변수에 저장됨 값이 저장된 메모리 주소(참조)가 변수에 저장됨
변경 가능성 불변 (한 번 생성되면 변경 불가능) 가변 (객체나 배열의 속성을 수정할 수 있음)
값 복사 방식 값 자체를 복사하여 독립된 변수를 생성 메모리 주소를 복사하여 같은 객체나 배열을 참조
메모리 사용 방식 값 자체가 작고 고정되어 있어 메모리 사용량이 적음 객체나 배열의 크기에 따라 메모리 사용량이 큼
예시 데이터 유형 string, number, boolean, null, undefined, symbol object, array, function

 

참고: 

 

26. Axios 에 대해 설명

✅ 답변

axios는 브라우저와 node.js에서 사용할 수 있는 Promise 기반 HTTP 비동기 통신 라이브러리입니다. axios 라이브러리의 장점은 다음과 같습니다. 

  • 서버에서 받은 JSON 응답을 JavaScript 객체로 자동 변환하여 제공하기 때문에 JSON 처리가 간편합니다.
  • 다양한 브라우저 및 환경에서 사용할 수 있으며, 간편하고 유연한 API를 제공합니다.
  • Promise 기반의 비동기 처리 방식을 지원하기 때문에 비동기 코드를 작성할 때 간결하고 직관적입니다.
  • 요청과 응답을 인터셉트하고, 요청 취소 및 에러 처리 등 다양한 기능을 제공하여 개발자가 효율적으로 작업할 수 있습니다.

참고: Axios 공식문서

 

 

27. 타입 추론에 대해 아는 대로 설명해주세요.

✅ 답변

타입 추론이란 타입이 지정되지 않은 변수의 타입을 타입스크립트가 자동으로 추론하는 기능을 의미합니다.

let x = 3;  // 타입 추론
let x: number = 3;  // 개발자가 타입 명시

위와 같이 x에 대한 타입을 따로 지정하지 않더라도, x는 타입스크립트 컴파일러에 의해 number로 간주됩니다.

이렇게 변수를 선언하거나 초기화 할 때 타입이 추론됩니다. 이외에도 변수, 속성, 인자의 기본 값, 함수의 반환 값 등을 설정할 때 타입 추론이 일어납니다.

 

참고:

 

28. Type과 Interface의 차이

✅ 답변

type과 interface는 모두 객체, 함수, 배열 등 여러 가지 형태의 타입을 정의할 수 있는 방법입니다.

하지만 두 가지는 몇 가지 차이점이 있으며, 각각의 용도와 상황에 맞게 사용할 수 있습니다.

 

1. 기본적인 역할

  • interface: 객체의 구조를 정의하는데 주로 사용됩니다. TypeScript는 주로 클래스와 관련된 타입 정의나 객체 모양을 설명할 때 interface를 사용하길 권장합니다.
  • type: 보다 범용적인 타입 정의 도구로, 객체뿐만 아니라 유니언 타입, 교차 타입, 기본 타입 등의 다양한 형태로 사용할 수 있습니다.

2. 확장성 (확장 및 구현)

  • interface: 다른 인터페이스를 `extends`를 통해 상속하거나, 클래스를 `implements`로 구현할 수 있습니다. 이를 통해 객체 타입을 확장하거나 여러 인터페이스를 결합할 수 있습니다.
interface Employee extends Person {
  position: string;
}

class Developer implements Employee {
  name: string;
  age: number;
  position: string;

  constructor(name: string, age: number, position: string) {
    this.name = name;
    this.age = age;
    this.position = position;
  }
}
  • type: type은 확장을 위한 extends를 지원하지 않지만, `교차 타입(&)`을 사용하여 타입을 확장할 수 있습니다. 또한, 여러 타입을 결합할 때는 `|`를 사용해 유니언 타입을 정의할 수 있습니다.
type Employee = Person & {
  position: string;
}

const developer: Employee = {
  name: "John",
  age: 30,
  position: "Frontend Developer",
};

 

3. 유니언 타입과 튜플

  • type: 유니언 타입과 튜플 정의에 적합합니다. 예를 들어, 여러 타입 중 하나를 허용하거나 고정된 길이의 배열을 정의할 때는 type을 사용합니다.
type StringOrNumber = string | number; // 유니언 타입
type TupleType = [string, number]; // 튜플 타입
  • interface: interface는 유니언 타입을 정의할 수 없고 튜플도 정의할 수 없습니다. 대신 객체 형태의 타입 정의에 더 적합합니다.

4. 선언 병합 (Declaration Merging)

  • interface: 동일한 이름의 interface가 여러 번 선언되면, 자동으로 병합됩니다. 이는 라이브러리나 외부 모듈에서 타입 확장이 필요할 때 유용합니다.
interface Person {
  name: string;
}

interface Person {
  age: number;
}

// 결과적으로 Person 인터페이스는 name과 age 모두를 가지게 됩니다.
const person: Person = {
  name: "Alice",
  age: 25,
};
  • type: type은 동일한 이름으로 여러 번 선언할 수 없고, 병합되지도 않습니다. 한 번 선언된 type은 재정의가 불가능합니다.

5. Computed Properties 및 매핑된 타입

  • type: type을 사용하면 매핑된 타입과 같은 고급 기능을 활용할 수 있습니다.
type Keys = "name" | "age";
type Person = {
  [key in Keys]: string;
};
  • interface: interface는 매핑된 타입을 지원하지 않습니다.

6. 결론

  • interface는 객체의 모양(구조)을 정의하고 확장하거나 병합할 수 있는 기능을 제공합니다. 클래스의 구조를 설명하거나 객체의 형태를 설명하는 경우 주로 사용됩니다.
  • type 더 일반적인 타입 정의 도구로, 유니언 타입, 튜플, 매핑된 타입 등 다양한 타입을 정의할 수 있으며, 객체뿐만 아니라 다른 데이터 구조를 표현하는데 적합합니다.

따라서, 객체 타입을 정의하고 확장할 필요가 있는 경우interface 사용하는 것이 좋으며, 유니언 타입이나 튜플, 교차 타입 등을 사용할 때type 사용하는 것이 적합합니다.

 

 


 

앞으로 공부할 질문들

...

 

 

 

 

 

 

반응형