지난주에는 const와 불변성의 차이, 프로토타입 기반 상속, 런타임 타입 체크를 배웠습니다. 핵심만 빠르게 짚어보겠습니다.
const: 변수 바인딩(참조)만 고정 — 객체 내부 변경은 자유롭게
가능Object.freeze(): 최상위 프로퍼티만 동결 (중첩 객체는 여전히
변경 가능)useState의 방식[[Prototype]] →
Object.prototype 순서prototype vs [[Prototype]]: 함수만 prototype 프로퍼티를
가짐. new로 만든 인스턴스의 [[Prototype]]이 됨Person.prototype.greet처럼 prototype에
추가하면 모든 인스턴스가 공유 (복사 아님)typeof의 한계: null, 배열, Date 등 모두 'object' 반환Array.isArray() 사용Object.prototype.toString.call(value) —
'[object Array]' 등 반환Q1. 아래 코드에서 클로저가 만들어지는 위치를 찾고, makeCounter()를 두 번
호출했을 때 각 카운터가 독립적으로 동작하는 이유를 설명하세요.
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counterA = makeCounter();
const counterB = makeCounter();
console.log(counterA()); // ?
console.log(counterA()); // ?
console.log
클로저는 return function() { ... } 부분에서 만들어집니다.
makeCounter()를 호출할 때마다 새로운 렉시컬 환경(새로운 count 변수)이
생성됩니다. counterA와 counterB는 각각 별도의 렉시컬 환경을 기억하는
클로저이므로 독립적인 count를 가집니다.
Q2. async function foo() {"{"} return 1; {"}"} 에서 foo()의 반환값
타입과 실제 값을 확인하고, 왜 그런지 설명하세요.
async function foo() {
return 1;
}
const result = foo();
console.log(result);
console.log(result instanceof Promise);
정답: result는 Promise 객체, instanceof Promise는 true
async 함수는 반환값을 항상 Promise로 감쌉니다. return 1을 해도 자동으로
Promise.resolve(1)로 래핑됩니다. 이미 Promise를 반환하면 이중 래핑하지
않습니다. async 함수의 결과를 사용하려면
await를 쓰거나 .then()으로 받아야 합니다.
Q3. Promise.all()로 병렬 실행할 때와 순차적으로 await를 연달아 쓸 때의
실행 시간 차이를 설명하세요. 각각 1초 걸리는 비동기 작업 2개라면 총 시간은
어떻게 다를까요?
await: 약 2초 (1초 + 1초)Promise.all(): 약 1초 (동시 시작, 가장 오래 걸리는
작업만큼만 소요)const a = await fetchA(); const b = await fetchB();는 fetchA가 완료된
후에야 fetchB를 시작합니다. 작업 간 의존성이 없다면
Promise.all()을 써야 합니다. 단, 하나라도 reject되면
전체가 reject되므로, 실패를 개별 처리해야 하면 Promise.allSettled()를
사용합니다.
Q4. 아래 코드의 출력 순서를 예측하고, 이벤트 루프의 마이크로태스크 큐와 매크로태스크 큐의 우선순위를 사용해 설명하세요.
console.log("A");
setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("D");
동기 코드인 A와 D가 먼저 실행됩니다. 이후 콜 스택이 비면
마이크로태스크 큐(Promise.then)가 먼저 처리되어 C가
출력되고, 그 다음 매크로태스크 큐(setTimeout)에서 B가 출력됩니다.
setTimeout의 지연 시간이 0이라도 매크로태스크이므로 마이크로태스크보다
늦게 실행됩니다.
이번 주 범위와 관련된 실제 면접 질문들입니다. 정답을 외우기보다는 자신만의 말로 설명하는 연습을 해보세요.
Q1. 클로저(Closure)란 무엇인가요? 어떤 경우에 활용하나요?
클로저는
함수가 선언된 렉시컬 환경을 기억하여, 외부 함수가 종료된 후에도 그 환경의 변수에 접근할 수 있는 것
입니다. 렉시컬 스코프의 자연스러운 결과물입니다.
function makeCounter() {
let count = 0;
return function () {
return ++count;
};
}
const counter = makeCounter(); // makeCounter 종료 후에도
counter(); // 1 — count 변수를 기억
counter(); // 2
counter(); // 3
makeAdder(5) 등)면접 팁: "React의 useState와 useEffect도 클로저를 기반으로 동작합니다. useEffect 의존성 배열을 빠뜨리면 오래된 클로저(stale closure)를 참조하는 버그가 생기는데, 이것도 클로저의 특성 때문입니다"라고 연결하면 좋습니다.
Q2. 콜백 지옥이란 무엇이고, Promise는 어떻게 해결하나요?
콜백 지옥은 비동기 작업을 순차적으로 처리할 때 콜백이 중첩되어 코드가 오른쪽으로 계속 깊어지는 현상입니다. 가독성이 떨어지고 에러 처리도 어렵습니다.
Promise는 비동기 작업의 결과를 값(객체)으로 표현하여 .then() 체이닝으로
수평적으로 나열할 수 있게 합니다. async/await은 Promise를 기반으로 동기
코드처럼 읽히도록 한 문법적 설탕입니다.
면접 팁: "async/await을 쓴다고 Promise를 몰라도 되는 것은 아닙니다.
Promise.all()로 병렬 처리를 하거나, 에러 처리를 정확히 하려면 Promise 동작
원리를 이해해야 합니다"라고 답하면 깊이 있는 인상을 줄 수 있습니다.
JS는 단일 스레드로 한 번에 하나의 작업만 실행합니다. 그럼에도 비동기 처리가 가능한 이유는 이벤트 루프 덕분입니다.
setTimeout, fetch 등 브라우저가 처리하는 비동기
작업setTimeout, setInterval 콜백이 대기Promise .then, queueMicrotask —
항상 태스크 큐보다 먼저 실행이벤트 루프는 콜 스택이 비면 마이크로태스크 큐 → 태스크 큐 순서로 꺼내 실행합니다.
면접 팁: "Promise.resolve().then()은 setTimeout(fn, 0)보다 먼저 실행됩니다. 마이크로태스크 큐가 태스크 큐보다 우선순위가 높기 때문입니다"라고 예시를 들면 좋습니다.
다음 주에는 실무에서 가장 많이 쓰는 JS 문법들을 집중적으로 다룹니다.
배열 고차함수(map, filter, reduce)와
ES6+ 문법
(구조 분해 할당, 스프레드, 옵셔널 체이닝, 모듈)입니다. React 코드를 읽고 쓸 때
거의 매 줄 등장하는 문법들이라 실무 체감이 가장 빠른 주차입니다.
다음 주 학습 자료(week9)의 얄코 강의를 시청하거나, 스킵 진단 문제 10개를 풀어보세요. 다음 주부터 React를 시작하므로, map/filter/구조 분해/스프레드 문법에 익숙해지면 React 코드가 훨씬 편하게 느껴질 것입니다.