Frontend

[Frontend] JavaScript 정복하기

teddy bear 2026. 7. 5. 08:08
반응형

⚡ 자바스크립트 핵심 개념 완전 정리

"자바스크립트는 10분 만에 배울 수 있지만, 제대로 이해하는 데는 10년이 걸린다."

프론트엔드 기술 면접에서 가장 자주 등장하는 것이 바로 자바스크립트 핵심 동작 원리입니다. 이 글에서는 면접에서 반드시 나오는 개념들을 원리 중심으로 정리합니다.


📌 목차

  1. 클로저 (Closure)
  2. 이벤트 루프 (Event Loop)
  3. 프로토타입 (Prototype)
  4. this 바인딩
  5. 호이스팅 (Hoisting)
  6. 비동기 처리: Promise와 async/await

1️⃣ 클로저 (Closure)

클로저는 함수가 자신이 선언될 당시의 렉시컬 스코프(Lexical Scope)를 기억해, 함수 실행이 끝난 후에도 그 스코프에 접근할 수 있는 현상입니다.

function makeCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

makeCounter() 실행이 끝나도 반환된 함수는 count 변수를 계속 기억합니다. 이것이 클로저입니다.

실무 활용 예시

  • 프라이빗 변수 구현 (모듈 패턴)
  • 디바운스/쓰로틀 함수 구현
  • 리액트의 useState도 내부적으로 클로저 원리를 활용
// 디바운스 함수 - 클로저로 timer를 유지
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

💡 면접 포인트: "클로저가 무엇인가"보다 "왜 메모리 누수를 조심해야 하는가"까지 답할 수 있으면 좋습니다. 클로저에 계속 참조되는 변수는 가비지 컬렉션 대상에서 제외되기 때문입니다.


2️⃣ 이벤트 루프 (Event Loop)

자바스크립트는 싱글 스레드로 동작하지만, setTimeout, fetch 같은 비동기 작업을 어떻게 논블로킹으로 처리할까요? 그 답이 이벤트 루프입니다.

┌───────────────┐
│   Call Stack   │ ← 동기 코드가 실행되는 곳
└───────┬───────┘
        │ 비어있으면
        ▼
┌───────────────┐      ┌──────────────────┐
│  Event Loop    │◀────│  Microtask Queue  │ ← Promise 콜백
└───────┬───────┘      └──────────────────┘
        │                      ▲
        ▼                      │
┌───────────────┐      ┌──────────────────┐
│  Task Queue    │─────▶│  Web APIs         │ ← setTimeout, fetch 등
└───────────────┘      └──────────────────┘

실행 순서 예시

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// 출력 순서: 1 → 4 → 3 → 2

핵심 원리

  1. 동기 코드(Call Stack)가 먼저 모두 실행된다 (1, 4)
  2. Call Stack이 비면, Microtask Queue(Promise)를 먼저 모두 비운다 (3)
  3. 그 다음 Task Queue(setTimeout 등)를 하나씩 처리한다 (2)

💡 면접 포인트: "Microtask가 Task보다 우선순위가 높다"는 점을 코드 실행 순서 예제로 설명할 수 있어야 합니다.


3️⃣ 프로토타입 (Prototype)

자바스크립트는 클래스 기반이 아닌 프로토타입 기반 상속 언어입니다. 모든 객체는 자신의 프로토타입을 가지며, 프로퍼티를 찾을 때 프로토타입 체인을 따라 올라갑니다.

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound.`;
};

const dog = new Animal('Dog');
console.log(dog.speak()); // "Dog makes a sound."
dog ──[[Prototype]]──▶ Animal.prototype ──[[Prototype]]──▶ Object.prototype ──▶ null

dog 객체에 speak가 없으면, 프로토타입 체인을 따라 Animal.prototype에서 찾습니다.

ES6 class 문법은 이 프로토타입 방식을 감싸는 **문법적 설탕(Syntactic Sugar)**일 뿐, 내부 동작 원리는 동일합니다.

class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a sound.`; }
}

4️⃣ this 바인딩

this는 함수가 호출되는 방식에 따라 값이 결정됩니다 (선언 위치가 아님, 단 화살표 함수는 예외).

호출 방식this 값

일반 함수 호출 undefined (strict mode) 또는 전역 객체
메서드 호출 (obj.method()) obj
call/apply/bind 명시적으로 지정한 객체
화살표 함수 선언 시점의 상위 스코프의 this를 그대로 사용
생성자 함수 (new) 새로 생성된 인스턴스
const obj = {
  name: 'Kim',
  regular: function () { console.log(this.name); },
  arrow: () => { console.log(this.name); },
};

obj.regular(); // "Kim" (obj가 this)
obj.arrow();   // undefined (선언 시점 상위 스코프의 this)

💡 면접 포인트: 콜백 함수 안에서 this가 예상과 다르게 동작하는 문제를 화살표 함수나 bind로 어떻게 해결하는지 설명할 수 있어야 합니다.


5️⃣ 호이스팅 (Hoisting)

변수와 함수 선언이 스코프의 최상단으로 끌어올려지는 것처럼 동작하는 현상입니다.

console.log(a); // undefined (에러 아님!)
var a = 10;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;

핵심 차이

  • var: 선언과 초기화(undefined)가 함께 호이스팅됨 → 접근 시 에러 없이 undefined
  • let/const: 선언만 호이스팅되고 초기화는 실제 코드 위치에서 이루어짐 → 그 사이 구간을 **TDZ(Temporal Dead Zone)**라 부르며, 접근 시 에러 발생
TDZ 시작        실제 선언문       TDZ 끝
   │                │
   ▼                ▼
{ let b; ... 접근 불가 구간 ... let b = 20; }

6️⃣ 비동기 처리: Promise와 async/await

콜백 지옥 문제

getUser(id, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      console.log(comments); // 들여쓰기 지옥
    });
  });
});

Promise로 개선

getUser(id)
  .then((user) => getPosts(user.id))
  .then((posts) => getComments(posts[0].id))
  .then((comments) => console.log(comments))
  .catch((err) => console.error(err));

async/await로 더 개선

async function loadComments(id) {
  try {
    const user = await getUser(id);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    console.log(comments);
  } catch (err) {
    console.error(err);
  }
}

async/await는 Promise를 동기 코드처럼 읽히게 해주는 문법적 설탕입니다. 내부적으로는 여전히 Promise 기반으로 동작합니다.

💡 면접 포인트: Promise.all과 Promise.allSettled의 차이(하나라도 실패하면 전체 실패 vs 모든 결과를 받음)를 설명할 수 있으면 좋은 인상을 줍니다.


🔚 마무리

자바스크립트 면접 질문들은 대부분 "왜 이렇게 동작하는가"를 묻습니다. 단순 암기보다 직접 코드를 실행해보며 결과를 예측하고 검증하는 연습이 가장 효과적인 준비 방법입니다.